移动端适配方案荟萃
持续更新...
# 移动终端布局适配方案
移动端适配--
# 适配方案分类
配置 HTML root 的 font-size
- px
- 百分比
%
- rem
- vw|vh
改变 meta viewport 的比例
- 缺点是不能兼容所有终端
# 移动端设计-前端适配步骤:
第一步,视觉设计阶段,设计师按宽度 750px(iPhone6)做设计稿,除图片外所有设计元素用矢量路径来做。设计定稿后在 750px 的设计稿上做标注,输出标注图。同时等比放大 1.5 倍生成宽度 1125px 的设计稿,在 1125px 的稿子里切图。
第二步,输出两个交付物给开发工程师:一个是程序用到的@3x 切图资源,另一个是宽度 750px 的设计标注图。
第三步,开发工程师拿到 750px 标注图和@3x 切图资源,完成 iPhone6(375pt)的界面开发。此阶段不能用固定宽度的方式开发界面,得用自动布局(auto layout),方便后续适配到其它尺寸。
第四步,适配调试阶段,基于 iPhone 6 的界面效果,分别向上向下调试 iPhone 6 plus(414pt)和 iPhone 5S 及以下(320pt)的界面效果。由此完成大中小三屏适配。
注:第三步,就要使用我们以上介绍的网易跟淘宝的适配方法了。假如公司设计稿不是基于 750 的怎么,其实很简单,按上图做一些相应替换即可,但是流程和方法还是一样的。解释一下为什么要在@3x 的图里切,这是因为现在市面上也有不少像魅蓝 note 这种超高清屏幕,devicePixelRatio 已经达到 3 了,这个切图保证在所有设备都清晰显示。
# rem 适配方案
# css 适配
/* px 方案 */
html {
font-size: 24px;
}
@media screen and (min-width: 320px) {
html {
font-size: 50px;
}
}
@media screen and (min-width: 360px) {
html {
font-size: 56.25px;
}
}
@media screen and (min-width: 375px) {
html {
font-size: 58.59375px;
}
}
@media screen and (min-width: 400px) {
html {
font-size: 62.5px;
}
}
@media screen and (min-width: 414px) {
html {
font-size: 64.6875px;
}
}
@media screen and (min-width: 440px) {
html {
font-size: 68.75px;
}
}
@media screen and (min-width: 480px) {
html {
font-size: 75px;
}
}
@media screen and (min-width: 520px) {
html {
font-size: 81.25px;
}
}
@media screen and (min-width: 560px) {
html {
font-size: 87.5px;
}
}
@media screen and (min-width: 600px) {
html {
font-size: 93.75px;
}
}
@media screen and (min-width: 640px) {
html {
font-size: 100px;
}
}
@media screen and (min-width: 680px) {
html {
font-size: 106.25px;
}
}
@media screen and (min-width: 720px) {
html {
font-size: 112.5px;
}
}
@media screen and (min-width: 760px) {
html {
font-size: 118.75px;
}
}
@media screen and (min-width: 800px) {
html {
font-size: 125px;
}
}
@media screen and (min-width: 960px) {
html {
font-size: 150px;
}
}
/* 百分比方案:
由于浏览器默认字体大小为
16px,所以当我们使用百分比作为根节点html的字体大小时,
即html元素的font-size值设置为一个百分比值,
rem的计算方式就会改为:
*/
@media screen and (min-width: 320px) {
html {
font-size: 312.5%;
}
}
@media screen and (min-width: 360px) {
html {
font-size: 351.5625%;
}
}
@media screen and (min-width: 375px) {
html {
font-size: 366.211%;
}
}
@media screen and (min-width: 400px) {
html {
font-size: 390.625%;
}
}
@media screen and (min-width: 414px) {
html {
font-size: 404.2969%;
}
}
@media screen and (min-width: 440px) {
html {
font-size: 429.6875%;
}
}
@media screen and (min-width: 480px) {
html {
font-size: 468.75%;
}
}
@media screen and (min-width: 520px) {
html {
font-size: 507.8125%;
}
}
@media screen and (min-width: 560px) {
html {
font-size: 546.875%;
}
}
@media screen and (min-width: 600px) {
html {
font-size: 585.9375%;
}
}
@media screen and (min-width: 640px) {
html {
font-size: 625%;
}
}
@media screen and (min-width: 680px) {
html {
font-size: 664.0625%;
}
}
@media screen and (min-width: 720px) {
html {
font-size: 703.125%;
}
}
@media screen and (min-width: 760px) {
html {
font-size: 742.1875%;
}
}
@media screen and (min-width: 800px) {
html {
font-size: 781.25%;
}
}
@media screen and (min-width: 960px) {
html {
font-size: 937.5%;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# js 方案
// 方案一
function adapt(designWidth, rem2px) {
var d = window.document.createElement('div');
d.style.width = '1rem';
d.style.display = 'none';
var head = window.document.getElementsByTagName('head')[0];
head.appendChild(d);
var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
d.remove();
document.documentElement.style.fontSize =
(((window.innerWidth / designWidth) * rem2px) / defaultFontSize) * 100 + '%';
var st = document.createElement('style');
var portrait =
'@media screen and (min-width: ' +
window.innerWidth +
'px) {html{font-size:' +
(window.innerWidth / (designWidth / rem2px) / defaultFontSize) * 100 +
'%;}}';
var landscape =
'@media screen and (min-width: ' +
window.innerHeight +
'px) {html{font-size:' +
(window.innerHeight / (designWidth / rem2px) / defaultFontSize) * 100 +
'%;}}';
st.innerHTML = portrait + landscape;
head.appendChild(st);
return defaultFontSize;
}
var defaultFontSize = adapt(640, 100);
// 方案二
var docEl = document.documentElement || document.body;
var resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize';
var reCalc = function() {
var clientWidth = docEl.clientWidth,
PIX = 750;
clientWidth = clientWidth > PIX ? PIX : clientWidth;
var fontSize = 100 * (clientWidth / PIX);
window.baseFontSize = fontSize;
docEl.style.fontSize = fontSize + 'px';
};
reCalc();
window.addEventListener(resizeEvt, reCalc, false);
document.addEventListener('DOMContentLoaded', reCalc, false);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# meta 标签
元素可提供有关页面的元信息。设置得当能大大提高 seo 推广
# viewport
<!--优先使用最新版本 IE 和 Chrome-->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!--H5页面窗口自动调整到设备宽度,并禁止用户缩放页面-->
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"
/>
<!--忽略数字识别为电话号码-->
<meta name="format-detection" content="telephone=no" />
<!--忽略邮箱地址的识别-->
<meta name="format-detection" content="email=no" />
<!--百度禁止转码-->
<meta http-equiv="Cache-Control" content="no-siteapp" />
<!--IOS启用 WebApp 全屏模式-->
<!--当网站添加到主屏幕后再点击进行启动时,可隐藏地址栏(从浏览器跳转或输入链接进入并没有此效果)-->
<meta name="apple-touch-fullscreen" content="yes" />
<!-- start -->
<!-- 只有生效时: safari 是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏,听说在IOS7以上版本就没效果了 -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- 则生效:可选default、black、black-translucent 但是我都是用black-->
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- end -->
<!--去掉winphone系统a、input标签被点击时产生的半透明灰色背景-->
<meta name="msapplication-tap-highlight" content="no" />
<!--iphoneX的齐刘海-->
<!-- 1.meta 设置可伸缩 -->
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no,minimal-ui,viewport-fit=cover"
/>
<style>
/* 2-1.若全部body元素增加样式 */
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
/* 2-2.若局部组件有fixed底部的元素,也增加上面样式 */
.component {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
background-color: #fff; /* 记得添加background-color,不然会出现透明镂空的情况 */
}
</style>
<!-- iphoneX的齐刘海 end -->
<!--IOS启用 WebApp 全屏模式-->
<!--当网站添加到主屏幕后再点击进行启动时,可隐藏地址栏(从浏览器跳转或输入链接进入并没有此效果)-->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-touch-fullscreen" content="yes" />
<!--ios添加到主屏后的标题-->
<meta name="apple-mobile-web-app-title" content="标题" />
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# CSS(层叠样式表)
层叠样式表 (Cascading Style Sheets,缩写为 CSS),是一种 样式表 语言,用来描述 HTML 或 XML(包括如 SVG、MathML、XHTML 之类的 XML 分支语言)文档的呈现。CSS 描述了在屏幕、纸质、音频等其它媒体上的元素应该如何被渲染的问题。
CSS 是开放网络的核心语言之一,由 W3C 规范 实现跨浏览器的标准化。
CSS Level 1 -> CSS1
CSS 等级(2021-05-27):
- CSS1 现已废弃
- CSS2.1 是推荐标准
- CSS3 分成多个小模块且正在标准化中。
# position:fixed
失效问题
在一次开发时可能无意中手抖在弹窗主体代码上敲了 transform:translate3d(0,0,0)
,之后在一次测试时打开弹窗发现弹窗定位失效了。
在定位了问题后最终发现是transform:translate3d(0,0,0)
引起的。
事后查阅 MDN - position:fixed 的定义:元素会被移出正常文档流,并不为元素预留空间,而是通过指定元素相对于屏幕视口(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变。打印时,元素会出现在的每页的固定位置。fixed 属性会创建新的层叠上下文。当元素的祖先元素含有的 transform, perspective 或 filter 属性非 none 时,元素容器由相对视口来定位改为该祖先元素来定位。
总结来讲就是:元素设置 position:fixed
的情况下,若元素的先祖元素含有 transform, perspective 或 filter
属性值为 none
是,元素的布局对象
由相对于屏幕视口
改为相对于该先祖元素
。
另外,我查阅其他文档发现一下属性也会引起元素 fixed 失效:
- will-change 中指定了任意 CSS 属性
- 设置了 transform-style: preserve-3d 的元素
当然实测不同浏览器的表现也不同,以上五种情况仅在 Chrome 中表现该特性。
# 层叠上下文 stack context
在 CSS 2.1 中, 所有的盒模型元素都处于三维坐标系中。 除了我们常用的横坐标和纵坐标, 盒模型元素还可以沿着“z 轴”层叠摆放, 当他们相互覆盖时, z 轴顺序就变得十分重要。
通常情况下,HTML 页面可以被认为是二维的,因为文本,图像和其他元素被排列在页面上而不重叠。在这种情况下,只有一个渲染进程,所有元素都知道其他元素所占用的空间。 z-index 属性可让你在渲染内容时调整对象分层的顺序。
堆叠上下文是 HTML 元素的三维概念,指假定 HTML 元素在一条虚构的相对于(电脑屏幕的)视窗或者网页,面向的用户的 z 轴上延伸的层叠结构,HTML 元素基于其元素属性按照优先级顺序占据这个层叠上下文空间。
在 CSS 方案中有一个特殊属性 z-index
可以指定元素的层叠上下文结构改变渲染顺序。除此之外,文档中的层叠上下文可以由满足以下任意一个条件的元素形成:
- 文档根元素();
- position 值为 absolute(绝对定位)或 relative(相对定位)且 z-index 值不为 auto 的元素;
- position 值为 fixed(固定定位)或 sticky(粘滞定位)的元素(沾滞定位适配所有移动设备上的浏览器,但老的桌面浏览器不支持);
- flex (flexbox (en-US)) 容器的子元素,且 z-index 值不为 auto;
- grid (grid) 容器的子元素,且 z-index 值不为 auto;
- opacity 属性值小于 1 的元素(参见 the specification for opacity);
- mix-blend-mode 属性值不为 normal 的元素;
- 以下任意属性值不为 none 的元素:
- transform
- filter
- perspective
- clip-path
- mask / mask-image / mask-border
- isolation 属性值为 isolate 的元素;
- -webkit-overflow-scrolling 属性值为 touch 的元素;
- will-change 值设定了任一属性而该属性在 non-initial 值时会创建层叠上下文的元素(参考这篇文章);
- contain 属性值为 layout、paint 或包含它们其中之一的合成值(比如 contain: strict、contain: content)的元素。
在层叠上下文中,子元素同样也按照上面解释的规则进行层叠。 重要的是,其子级层叠上下文的 z-index 值只在父级中才有意义。子级层叠上下文被自动视为父级层叠上下文的一个独立单元。
- 层叠上下文可以包含在其他层叠上下文中,并且一起创建一个层叠上下文的层级。
- 每个层叠上下文都完全独立于它的兄弟元素:当处理层叠时只考虑子元素。
- 每个层叠上下文都是自包含的:当一个元素的内容发生层叠后,该元素将被作为整体在父级层叠上下文中按顺序进行层叠。
注:层叠上下文的层级是 HTML 元素层级的一个子级,因为只有某些元素才会创建层叠上下文。可以这样说,没有创建自己的层叠上下文的元素会被父层叠上下文同化。
# 实验室
对于上述元素属性,下面做一些实验,可以在不同浏览器打开测试
See the Pen 基于不同浏览器内核,测试层叠上下文(Stacking Contex)对 fixed 定位的影响 by stephen l (@liejiayong) on CodePen.
# 相关链接
# 移动端点击样式闪动
.tap-noflashing {
-webkit-tap-highlight-color: rgba (255, 255, 255, 0);
-webkit-tap-highlight-color: transparent; /* i.e . Nexus5/Chrome and Kindle Fire HD 7 '' */
}
2
3
4
# 屏蔽用户选择
.user-noselect {
-webkit-touch-callout: none; // 可禁止保存或拷贝图像
-webkit-user-select: none;
-khtml-user-select: none; /* ios os */
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
2
3
4
5
6
7
8
# 禁止文本缩放
<!-- css 方案 -->
<style>
.text-noscale {
-webkit-text-size-adjust: 100%;
-webkit-text-size-adjust: 100% !important; /* 若用户设置字号放大或者缩小导致页面布局错误 */
text-size-adjust: 100% !important;
-moz-text-size-adjust: 100% !important;
}
</style>
<script>
/* js 方案:设置webview字体大小不受系统修改而改变 */
(function() {
if (window.HiSpaceObject) {
window.HiSpaceObject.setTextSizeNormal();
}
})();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 解决字体在移动端比例缩小后出现锯齿的问题
-webkit-font-smoothing: antialiased;
# 手机拍照和上传图片
input 有 capture 属性,取值:camera:相机;camcorder:摄像;microphone:录音
使用的 accept 属性可以控制类型
<!-- 选择照片 -->
<input type="file" accept="image/*" />
<!-- 选择视频 -->
<input type="file" accept="video/*" />
<script>
// 另外在移动端唤醒拍照时,在实际开发中,input在默认不设置capture情况下QQ只能调用相册,因此需要写个兼容 const isQQ =
var isQQ = (function() {
const u = navigator.userAgent;
let match = u.match(/QQ\//i);
match = match ? match[0] : false;
return match == 'QQ/';
})();
if (isQQ) {
document.getElementById('wapInput').setAttribute('capture', 'camera');
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# h5 端锁定屏幕与页面跟随旋转方向的问题
# css 用 css3 媒体查询,缺点是宽度和高度不好控制
@media screen and (orientation: portrait) {
.main {
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
transform: rotate(-90deg);
width: 100vh;
height: 100vh;
/*去掉overflow 微信显示正常,但是浏览器有问题,竖屏时强制横屏缩小*/
overflow: hidden;
}
}
@media screen and (orientation: landscape) {
.main {
-webkit-transform: rotate(0);
-moz-transform: rotate(0);
-ms-transform: rotate(0);
transform: rotate(0);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# js 判断屏幕的方向或者 resize 事件
window.orientation,取值:
- 正负 90 表示横屏模式
- 0 和 180 表现为竖屏模式
window.onorientationchange = function() {
switch (window.orientation) {
case -90:
case 90:
alert('横屏:' + window.orientation);
case 0:
case 180:
alert('竖屏:' + window.orientation);
break;
}
};
var evt = 'onorientationchange' in window ? 'orientationchange' : 'resize';
window.addEventListener(
evt,
function() {
var width = document.documentElement.clientWidth;
var height = document.documentElement.clientHeight;
$print = $('#print');
if (width > height) {
$print.width(width);
$print.height(height);
$print.css('top', 0);
$print.css('left', 0);
$print.css('transform', 'none');
$print.css('transform-origin', '50% 50%');
} else {
$print.width(height);
$print.height(width);
$print.css('top', (height - width) / 2);
$print.css('left', 0 - (height - width) / 2);
$print.css('transform-origin', '50% 50%');
}
},
false
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# -webkit
# ::-webkit-scrollbar
::-webkit-scrollbar 滚动条整体部分
::-webkit-scrollbar-thumb 滚动条里面的小方块,能上下左右移动(取决于是垂直滚动条还是水平滚动条)
::-webkit-scrollbar-track 滚动条的轨道(里面装有thumb)
::-webkit-scrollbar-button 滚动条轨道两端的按钮,允许通过点击微调小方块的位置
::-webkit-scrollbar-track-piece 内层轨道,滚动条中间部分(除去)
::-webkit-scrollbar-corner 边角,及两个滚动条的交汇处
::-webkit-resizer 两个滚动条的交汇处上用于通过拖动调整元素大小的小控件
/*定义滚动条的高宽*/
::-webkit-scrollbar {
width: 17px;
height: 17px;
}
/*CSS的坐标系,左上角为(0,0),往右往下为增加,往上往左为减少*/
/*显示滚动条上方的渐增按钮*/
::-webkit-scrollbar-button:start:decrement,
/*显示滚动条上方的渐减按钮*/
::-webkit-scrollbar-button:end:increment {
display: block;
}
/*隐藏滚动条上方的渐增按钮*/
::-webkit-scrollbar-button:vertical:start:increment,
::-webkit-scrollbar-button:vertical:end:decrement {
display: none;
}
/* 定义滚动条渐增按扭的样式 */
::-webkit-scrollbar-button:end:increment {
background-image: url(../img/scroll_cntrl_dwn.png);
}
/* 定义滚动条渐减按扭的样式 */
::-webkit-scrollbar-button:start:decrement {
background-image: url(../img/scroll_cntrl_up.png);
}
/* 垂直滚动条的第三层轨道的上段 */
::-webkit-scrollbar-track-piece:vertical:start {
background-image: url(../img/scroll_gutter_mid.png);
background-repeat: repeat-y;
}
/* 垂直滚动条的第三层轨道的下段 */
::-webkit-scrollbar-track-piece:vertical:end {
background-image: url(../img/scroll_gutter_mid.png);
background-repeat: repeat-y;
background-position: bottom left, 0 0;
}
/* 垂直滚动条的滑动块 */
::-webkit-scrollbar-thumb:vertical {
height: 36px;
-webkit-border-image: url(../img/scroll_thumb.png) 0 stretch stretch;
border-width: 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# ::webkit-input-placeholder
::-webkit-input-placeholder {
/* Chrome/Opera/Safari */
color: pink;
}
::-moz-placeholder {
/* Firefox 19+ */
color: pink;
}
:-ms-input-placeholder {
/* IE 10+ */
color: pink;
}
:-moz-placeholder {
/* Firefox 18- */
color: pink;
}
@mixin inputColor($color: #e28d2a) {
::-webkit-input-placeholder {
color: $color;
}
::-moz-placeholder {
color: $color;
}
::-ms-input-placeholder {
color: $color;
}
::-moz-placeholder {
color: $color;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 背景渐变兼容性
FILTER(ie 模式):
IE 6 7 8:FILTER: progid:DXImageTransform.Microsoft.Gradient(gradientType0,startColorStr=#b8c4cb,endColorStr=red);
IE 10:background: -ms-linear-gradient(top, #fff, #0000ff);
火狐模式:background:-moz-linear-gradient(top,#b8c4cb,#f6f6f8);
谷歌模式:background:-webkit-gradient(linear, 0% 0%, 0% 100%,from(#b8c4cb), to(#f6f6f8));
Safari 4-5, Chrome 1-9:background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#0000ff)); Safari5.1 Chrome 10+:background: -webkit-linear-gradient(top, #fff, #0000ff);
Opera 11.10+:background: -o-linear-gradient(top, #fff, #0000ff);
@mixin gradientBg($bg){
FILTER: progid:DXImageTransform.Microsoft.Gradient(gradientType0,startColorStr=#b8c4cb,endColorStr=red);
-ms-linear-gradient(top, #fff, #0000ff);
background:-moz-linear-gradient(top,#b8c4cb,#f6f6f8);
background:-webkit-gradient(linear, 0% 0%, 0% 100%,from(#b8c4cb), to(#f6f6f8));
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#0000ff));
background: -webkit-linear-gradient(top, #fff, #0000ff);
background: -o-linear-gradient(top, #fff, #0000ff);
}
2
3
4
5
6
7
8
9
10
11
# 文本溢出省略特性
- 对于单行文本,使用单行省略:
@mixin ellipsis() {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
2
3
4
5
- 对于多行文本的超长省略,则需要使用 -webkit-line-clamp 相关属性来实现,兼容性移动端基本没问题,pc 端则需要考虑 ie 浏览器
@mixin ellipsis($line: 1) {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $line;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
}
2
3
4
5
6
7
8
在实际开发中,需求总是千奇百怪的,当我们需要换行显示的内容不只有纯文本还包括一些内联块元素时,默认设置-webkit-line-clamp 的超长省略也不会出现你想要的效果,如下案例一:
<section class="juser">
<a href="/" class="avatar"></a>
<div class="info">
<p class="name">JYLie</p>
<p class="extral">
<span>Node.js</span>
<span>Javascript</span>
</p>
</div>
</section>
2
3
4
5
6
7
8
9
10
案例要实现 extral 里面的标签不换行显示,若内容过长就会出现文本溢出显示的情况,于是需要设置为文本内容过长发生溢出的部分使用...
来显示。案例一演示
主要得出以下几个问题:
- .extral 的 span 是 inline 的时候,超出文本设置为...不显示
- .extral 的 span 是 inline-block 的时候,超出文本直接裁剪不显示
- 当 .extral 设置为-webkit-box 且-webkit-clamp-line:1 时,span 为默认 inline 的情况下,span 会根据实际情况直接显示或隐藏为...
- 但是实际开发中发现 IOS 和 Safair 浏览器不支持本整块超长溢出打点省略,查看规范 - CSS Basic User Interface Module Level 3 - text-overflow,究其原因,在于 text-overflow 只能对内联元素进行打点省略(可能 Chrome 对此可能做了一些优化)。
- 解决方法:将 .extral 的 span 设置为 inline-block 即可,即下属结论。
- 但是实际开发中发现 IOS 和 Safair 浏览器不支持本整块超长溢出打点省略,查看规范 - CSS Basic User Interface Module Level 3 - text-overflow,究其原因,在于 text-overflow 只能对内联元素进行打点省略(可能 Chrome 对此可能做了一些优化)。
- 当 .extral 设置为-webkit-box 且-webkit-clamp-line:1 时,span 为默认 inline-block 的情况下,span 会根据实际情况直接显示或隐藏为...
# CSS 透明度颜色设置问题
Android 部分不支持 hex 写法,推荐用 rgba 的写法
#0000009c --> rgba(0, 0, 0, 0.61)
# 谨慎使用 fixed,以 absolute 代替
# webkit 表单输入框 placeholder 的文字能换行么 ios 可以,android 不行~
// 局部滚动卡顿
// 为防止一些bug,盒子使用touch之后,盒子直接为正常文档流而不设置position: relative
-webkit-overflow-scrolling: touch;
// 去掉元素被触摸时产生的半透明灰色遮罩
a,button,input,textarea{-webkit-tap-highlight-color: rgba(0,0,0,0;)}
// 默认input默认样式
input,button,textarea{-webkit-appearance: none;}
// android去掉语音输入按钮
input::-webkit-input-speech-button {display: none
<!--android 元素被点击时产生的边框怎么去掉-->
a,button,input,textarea{-webkit-tap-highlight-color: rgba(0,0,0,0;);-webkit-user-modify:read-write-plaintext-only; }
// iOS输入自动修正
<input type="text" autocorrect="off" />
// iOS键盘首字母自动大写
<input type="text" autocapitalize="off" />
// ios禁止文本缩放
-webkit-text-size-adjust: 100%;
<!--使用伪元素改变 IE10 表单元素默认外观-->
<!--ie select 默认下拉箭头-->
select::-ms-expand {display: none;}
<!--ie radio 和 checkbox-->
input[type=radio]::-ms-check,input[type=checkbox]::-ms-check{display: none;}
<!--ie 输入框-->
input[type=text]::-ms-clear,input[type=tel]::-ms-clear,input[type=number]::-ms-clear{display: none;}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 适配高清图片方案
准备至少 2 套图片来适配所有移动终端
- Media Query 查询设备分辨率(兼容所有设备)
- 对于正常分辨率<1.5
- 对于高清分辨率<1.5
- @media only screen and (min-device-pixel-ratio:1.5),(-webkit-min-device-pixel-ratio:1.5){}
- image-set 设置 retina 背景图(只兼容现代设备和浏览器)
.bg {
background-image: url('xxx.jpg') no-repeat;
background-image: -webkit-image-set(url('xxx.jpg') 1x, url('xxx@2x.jpg') 2x);
}
2
3
4
# 开启过渡动画硬件加速
- 优点:动画流畅
- 缺点:耗电
- 使用:transform: translate3d(0, 0, 0)
# 消除 transition 闪屏
.no-flash {
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
}
2
3
4
5
# 事件
# 移动端 滚动穿透问题
待更新
# 移动端 click 屏幕产生 200-300 ms 的延迟响应
这要追溯至 2007 年初。苹果公司在发布首款 iPhone 前夕,遇到一个问题:当时的网站都是为大屏幕设备所设计的。于是苹果的工程师们做了一些约定,应对 iPhone 这种小屏幕浏览桌面端站点的问题。
这当中最出名的,当属双击缩放(double tap to zoom),这也是会有上述 300 毫秒延迟的主要原因。
双击缩放,顾名思义,即用手指在屏幕上快速点击两次,iOS 自带的 Safari 浏览器会将网页缩放至原始比例。 那么这和 300 毫秒延迟有什么联系呢? 假定这么一个场景。用户在 iOS Safari 里边点击了一个链接。由于用户可以进行双击缩放或者双击滚动的操作,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。因此,iOS Safari 就等待 300 毫秒,以判断用户是否再次点击了屏幕。 鉴于 iPhone 的成功,其他移动浏览器都复制了 iPhone Safari 浏览器的多数约定,包括双击缩放,几乎现在所有的移动端浏览器都有这个功能。之前人们刚刚接触移动端的页面,在欣喜的时候往往不会 care 这个 300ms 的延时问题,可是如今 touch 端界面如雨后春笋,用户对体验的要求也更高,这 300ms 带来的卡顿慢慢变得让人难以接受。
解决方案有:
- 采用 touchstart 或者 touchend 代替 click
- 封装 tap 事件来代替 click 事件,因为 zeptojs 的 tap 存在
点击穿透
,因此需衡量 - 使用插件 FastClick.js --> FastClick.attach(document.body)
# 重力传感器事件 deviceMotion
# 媒体优化
# base64 编码图片替换 url 图片
- 建议 8K 以下的图标都转换成 base64,或使用字体图标代替,SVG 等来代替
# 图片懒加载
# 使用 img 还是 background
方式区别:
- img:html 中的标签 img 是网页结构的一部分会在加载结构的过程中和其他标签一起加载
- background:以 css 背景图存在的图片 background 会等到结构加载完成(网页的内容全部显示以后)才开始加载
按需选择
# 播放视频不全屏
- 目前只有 ios7+、winphone8+支持自动播放
- 支持 Airplay 的设备(如:音箱、Apple TV)播放 x-webkit-airplay="true"
- 播放视频不全屏,ios7、、winphone8+支持,部分 android4+支持(含华为、小米、魅族)webkit-playsinline="true"
- ios 10 : playsinline
- ios 8、9 :https://github.com/bfred-it/iphone-inline-video
- 兼容 webview 需要添加参数: webview.allowsInlineMediaPlayback = YES
<video x-webkit-airplay='true' webkit-playsinline='true' playsinline preload='auto' autoplay src='http://'></video>;
if (webview) webview.allowsInlineMediaPlayback = YES;
2
3
# ios 竖屏拍照上传,图片被旋转问题
步骤:
- 通过第三方插件 exif-js 获取到图片的方向
- new 一个 FileReader 对象,加载读取上传的图片 base64
- 在 fileReader 的 onload 函数中,得到的图片文件用一个 Image 对象接收
- 在 image 的 onload 函数中,利用步骤 1 中获取到的方向 orientation,通过 canvas 旋转校正,重新绘制一张新图
- 注意 iPhone 有 3 个拍照方向需要处理,横屏拍摄,home 键在左侧,竖屏拍摄,home 建上下
- 将绘制的新图转成 Blob 对象,添加到 FormData 对象中,然后进行校正后的上传操作
- 代码有点杂,待整理后上传,网上应该是可以找到的
# 部分手机第三方输入法会将页面网上挤的问题
1 // 特定需求页面,比如评论页面,输入框在顶部之类的
2 const interval = setInterval(function() {
3 document.body.scrollTop = 0;
4 }, 100)
5 // 注意关闭页面或者销毁组件的时候记得清空定时器
6 clearInterval(interval);
# 某些机型不支持 video 标签的 poster 属性,特别是安卓
用图片元素 来代替 poster
播放前显示,隐藏
播放后显示,隐藏
# flex 对低版本的 ios 和 Android 的支持问题
使用 postcss 的 autoprefixer 插件,自动添加浏览器内核前缀,并且增加更低浏览器版本的配置,自动添加 flex 老版本的属性和写法
autoprefixer({
"browsers": [
"iOS >= 6", // 特殊处理支持低版本IOS
"Safari >= 6" // 特殊处理支持低版本Safari
]
})
2
3
4
5
6
# ios 日期转换 NAN 的问题
将日期字符串的格式符号替换成'/'。
栗子:'yyyy-MM-dd'.replace(/-/g, '/')
# ios 页面回退到长列表出现灰色遮挡问题
- 方案 1:对列表数据进行缓存,比如 redux 之类的用法。
- 方案 2:回退时,跳到页面顶部。
const timer = setTimeout(() => {
window.scrollTo(0, 1);
window.scrollTo(0, 0);
clearTimeout(timer);
}, 0);
2
3
4
5
# 字体
# 默认字体
# ios
默认中文字体是 Heiti SC 默认英文字体是 Helvetica 默认数字字体是 HelveticaNeue 无微软雅黑字体
# android
默认中文字体是 Droidsansfallback 默认英文和数字字体是 Droid Sans 无微软雅黑字体
# winphone
默认中文字体是 Dengxian(方正等线体) 默认英文和数字字体是 Segoe 无微软雅黑字体
各个手机系统有自己的默认字体,且都不支持微软雅黑如无特殊需求,手机端无需定义中文字体,使用系统默认英文字体和数字字体可使用 Helvetica ,三种系统都支持
/* 移动端定义字体的代码 */
body{font-family:Helvetica;}
# webview
- android4.4 后,用 webview 加载网页,一定用同一协议请求
- 在 h5 页面嵌入 sdk 或 app 内页项目时,注意 app 端开发人员对 webview 状态、时间监听处理,排除不必要的麻烦
- 设置 webview 字体大小不受系统修改而改变
//设置webview字体大小不受系统修改而改变
(function() {
if (window.HiSpaceObject) {
window.HiSpaceObject.setTextSizeNormal();
}
})();
2
3
4
5
6
# bug log
# android 2.3 bug
- @-webkit-keyframes 需要以 0%开始 100%结束,0%的百分号不能去掉
- after 和 before 伪类无法使用动画 animation
- border-radius 不支持%单位
- translate 百分比的写法和 scale 在一起会导致失效,例如-webkit-transform: translate(-50%,-50%) scale(-0.5, 1)
# android 4.x bug
- 三星 Galaxy S4 中自带浏览器不支持 border-radius 缩写
- 同时设置 border-radius 和背景色的时候,背景色会溢出到圆角以外部分
- 部分手机(如三星),a 链接支持鼠标:visited 事件,也就是说链接访问后文字变为紫色 android 无法同时播放多音频 audio
# 解决在 ios 点击无反应的问题
css 属性:cuosor:pointer;以及 onclick="",这个空事件,原因是 ios 默认非点击标签不具有点击效果,所以给这些标签添加相关属性,这样系统可以识别出来!
# iOS 上拉边界下拉出现空白
手指按住屏幕下拉,屏幕顶部会多出一块白色区域。手指按住屏幕上拉,底部多出一块白色区域。
在 iOS 中,手指按住屏幕上下拖动,会触发 touchmove 事件。这个事件触发的对象是整个 webview 容器,容器自然会被拖动,剩下的部分会成空白。
document.body.addEventListener(
'touchmove',
function(e) {
if (e._isScroller) return;
// 阻止默认事件
e.preventDefault();
},
{
passive: false,
}
);
2
3
4
5
6
7
8
9
10
11
# ios 日期转换 NAN 的问题
将日期字符串的格式符号替换成'/'
'yyyy-MM-dd'.replace(/-/g, '/');
# 软键盘问题
# IOS 键盘弹起挡住原来的视图
- 可以通过监听移动端软键盘弹起 Element.scrollIntoViewIfNeeded(Boolean)方法用来将不在浏览器窗口的可见区域内的元素滚动到浏览器窗口的可见区域。 如果该元素已经在浏览器窗口的可见区域内,则不会发生滚动。 - true,则元素将在其所在滚动区的可视区域中居中对齐。 - false,则元素将与其所在滚动区的可视区域最近的边缘对齐。 根据可见区域最靠近元素的哪个边缘,元素的顶部将与可见区域的顶部边缘对准,或者元素的底部边缘将与可见区域的底部边缘对准。
window.addEventListener('resize', function() {
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
window.setTimeout(function() {
if ('scrollIntoView' in document.activeElement) {
document.activeElement.scrollIntoView(false);
} else {
document.activeElement.scrollIntoViewIfNeeded(false);
}
}, 0);
}
});
2
3
4
5
6
7
8
9
10
11
# onkeyUp 和 onKeydown 兼容性问题
IOS 中 input 键盘事件 keyup、keydown、等支持不是很好, 用 input 监听键盘 keyup 事件,在安卓手机浏览器中没有问题,但是在 ios 手机浏览器中用输入法输入之后,并未立刻相应 keyup 事件
# IOS12 输入框难以点击获取焦点,弹不出软键盘
定位找到问题是 fastclick.js 对 IOS12 的兼容性,可在 fastclick.js 源码或者 main.js 做以下修改
FastClick.prototype.focus = function(targetElement) {
var length;
if (
deviceIsIOS &&
targetElement.setSelectionRange &&
targetElement.type.indexOf('date') !== 0 &&
targetElement.type !== 'time' &&
targetElement.type !== 'month'
) {
length = targetElement.value.length;
targetElement.setSelectionRange(length, length);
targetElement.focus();
} else {
targetElement.focus();
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# IOS 键盘收起时页面没用回落,底部会留白
通过监听键盘回落时间滚动到原来的位置
window.addEventListener('focusout', function() {
window.scrollTo(0, 0);
});
//input输入框弹起软键盘的解决方案。
var bfscrolltop = document.body.scrollTop;
$('input')
.focus(function() {
document.body.scrollTop = document.body.scrollHeight;
//console.log(document.body.scrollTop);
})
.blur(function() {
document.body.scrollTop = bfscrolltop;
//console.log(document.body.scrollTop);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# IOS 下 fixed 失效的原因(面向底部有输入栏 input)
软键盘唤起后,页面的 fixed 元素将失效,变成了 absolute,所以当页面超过一屏且滚动时,失效的 fixed 元素就会跟随滚动了。不仅限于 type=text 的输入框,凡是软键盘(比如时间日期选择、select 选择等等)被唤起,都会遇到同样地问题。
解决方法: 不让页面滚动,而是让主体部分自己滚动,主体部分高度设为 100%,overflow:scroll
<body>
<div class='warper'>
<div class='main'></div>
<div>
<div class="fix-bottom"></div>
</body>
2
3
4
5
6
.warper {
position: absolute;
width: 100%;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow-y: scroll;
-webkit-overflow-scrolling: touch; /* 解决ios滑动不流畅问题 */
-webkit-transform: translate3d(0, 0, 0);
}
.fix-bottom {
position: fixed;
bottom: 0;
width: 100%;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# IOS 页面有菜单 MenuBar 的滚动和展示问题
如果使用 position: absolute 会出现 MenuBar 在 Safari 浏览器上被菜单栏遮盖的情况。因此需要使用一个父容器包裹内容,然后使用一个 main-wrapper 来主控滚动,menubar 紧跟下面
<div id="topWrapper" class="jy-wrapper">
<div id="mainWrapper" class="mscroll-wrapper">
<div id="home" class="jy-section section-1 "></div>
<div id="p1" class="jy-section section-2"></div>
<div id="p2" class="jy-section section-3"></div>
<div id="p3" class="jy-section section-4"></div>
</div>
<div id="menuWrapper" class="menubar">
<a href="javascript:;" data-type="home" class="jy-btn-txt active"><span>首页</span></a>
<a href="javascript:;" data-type="p1" class="jy-btn-txt"><span>希望种子</span></a>
<a href="javascript:;" data-type="p2" class="jy-btn-txt"><span>播种希望</span></a>
<a href="javascript:;" data-type="p3" class="jy-btn-txt"><span>希望之树</span></a>
</div>
</div>
<style>
html,
body {
height: 100%;
height: 100vh;
}
.jy-wrapper {
position: relative;
margin-left: auto;
margin-right: auto;
padding-bottom: env(safe-area-inset-bottom);
max-width: 750px;
height: 100%;
}
.mscroll-wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100%;
height: calc(100% - 1.5rem);
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
-webkit-transform: translate3d(0, 0, 0);
}
.menubar {
padding-bottom: env(safe-area-inset-bottom);
position: fixed;
left: 0;
top: calc(100% - 1.5rem);
z-index: 50;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
width: 7.5rem;
height: 1.5rem;
}
</style>
<script>
/**
* menu compatible
* @param {*} parentCls 父元素容器
* @param {*} scrollCls 滚动容器
* @param {*} menuCls 菜单容器
*/
var menusCompat = (function(parentCls, scrollCls, menuCls) {
parentCls = parentCls || '#topWrapper';
scrollCls = scrollCls || '#mainWrapper';
menuCls = menuCls || '#menuWrapper';
return function() {
var $parent = document.querySelector(parentCls),
$scroll = document.querySelector(scrollCls),
$menu = document.querySelector(menuCls),
wrapperHeight = $parent.offsetHeight - $menu.offsetHeight;
$parent.style.position = 'relative';
$scroll.style.height = wrapperHeight + 'px';
$menu.style.top = wrapperHeight + 'px';
};
})();
menusCompat(); // menusCompat 初始化
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# 解决 audio|video 媒体元素 不能自动播放的问题,以及按钮控制 audio 状态
在 ios 端播放媒体(音频\视频)时,市场会出现各种不播放或无法监听事件的问题,常见有下述几种:
- ios 不自动缓存媒体资源,audio 元素需要手动设置 preload='auto':
audio.setAttribute('preload', 'auto');
- 媒体资源设置 autoplay ios 端自动播放不生效问题
- 粗暴方法是监听 window,window 只要有触碰就会响起媒体自动播放。弊端是需要用户触碰了屏幕才播放:
$("document,body").one("click",function(){startPlay("andio awake by document,body click")})
- 用户体验好来讲即需要设置
点击播放场景
来播放媒体资源,常用是滑动界面或添加一个播放按钮。 - 在微信端,设置 autoplay 自动播放后 ios 端不自动播放时,可以设置检查微信内置事件
WeixinJSBridgeReady
来触发媒体播放,不过随着科技的进步及大视频的普及,多数 app 和浏览器终端都禁用进入页面自动播放媒体的操作,为了避免不同终端效果不一致,建议还是设置按钮触发播放为妙。
- 粗暴方法是监听 window,window 只要有触碰就会响起媒体自动播放。弊端是需要用户触碰了屏幕才播放:
- 媒体元素无法检查
canplay|loadedmetadata
等事件的情况。一般情况 android 没问题,ios 端无法监听,一般监听此类事件需要媒体元素加载了媒体资源 src,然后运行audio.load()|video.load()
,运行 audio.load()后设置事件监听即可顺利监听。
// 粗暴方法: 原理是使用用户点击来触发,但是还是建议设置按钮来引导点击播放
/* 微信端 */
document.addEventListener(
'WeixinJSBridgeReady',
function() {
audio.play();
},
false
);
/* android和ios触屏即播 */
$('document').one('touchstart', function() {
audio.play();
});
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* music compact
* @param {el} btnAudio
*/
var initMusic = function(btnAudio) {
var url = jtool.audioPath + 'bgm.mp3?v=0',
btnAudio = btnAudio || $('.btn-aud'),
audio = new Audio(),
delay = 2000;
audio.setAttribute('src', url);
audio.setAttribute('preload', 'auto'); /* ios自动缓冲,保险些可以设置audio.load()来加载缓冲 */
audio.setAttribute('loop', true);
// 静音状态
// audio.setAttribute('autoplay', true);
// audio.setAttribute('muted', false);
audio.volume = 0.5;
audio.style.cssText = ';opacity:.1;height:1px;';
document.addEventListener('DOMContentLoaded', function() {
document.body.appendChild(audio);
});
/* prettier-ignore */
window.setTimeout(function(){console.log("audio delay",delay);audio.load();audio.onloadedmetadata=function(){var duration=this.duration;startPlay("audio loadedmetadata total durtion is "+duration)};audio.addEventListener("canplay",function(){startPlay("andio canlpay")});if(typeof WinxinJSBridge=="object"&&typeof WeixinJSBridge.invoke=="function"){startPlay("wechat WeixinJSBridge is invoke")}else{if(document.addEventListener){document.addEventListener("WeixinJSBridgeReady",function(){startPlay("wechat WeixinJSBridgeReady by modernbrower")},false)}else{if(document.attachEvent){document.attachEvent("WeixinJSBridgeReady",function(){startPlay("wechat WeixinJSBridgeReady by iebrower")},false);document.attachEvent("onWeixinJSBridgeReady",function(){startPlay("wechat onWeixinJSBridgeReady by iebrower")},false)}}}$("document").one("click",function(){startPlay("andio awake by document click")});btnAudio.on("click",function(){play()})},delay);
var isPlay = false;
function startPlay(notice) {
if (isPlay) {
return console.log('audio first playing, not ', notice);
}
audio.play();
if (!audio.paused) {
isPlay = true;
}
console.log('audio initial status: ', isPlay, ' and', notice);
}
function play() {
if (audio.paused) {
btnAudio.addClass(jtool.activeCls);
audio.play();
} else {
btnAudio.removeClass(jtool.activeCls);
audio.pause();
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44