移动端前端适配方案总结

相关概念

在介绍各方案之前,先有必要了解一些必备的名词。

px

全称 pixel,像素。一个像素就是计算机屏幕所能显示一种特定颜色的最小区域。屏幕上显示数据最基本的点,不是长度单位。 如果点很小,那画面就清晰,我们称它为“分辨率高”,反之,就是“分辨率低”。

ppi

全称 Pixels Per Inch,屏幕像素密度。单位是 dpi,表示的是每英寸所拥有的像素(Pixel)数目。 越大屏幕越高清。

分辨率

屏幕分辨率确定计算机屏幕上显示多少信息的设置,以水平和垂直像素来衡量,iphone5 屏幕上垂直有 1136 个物理像素,水平有 640 个物理像素。查询设备的分辨率及 ppi

rem

相对长度单位。相对于根元素(即 html 元素)的 font-size 计算值的倍数。

em

相对长度单位。在 font-size 中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体大小,如 width

vw,vh

视窗宽度/高度的 1%

物理像素(设备像素)

设备屏幕上的实际像素。如 iphone6 宽为 750

设备独立像素(逻辑像素/css 像素)

设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: css 像素),然后由相关系统转换为物理像素。如 iphone6375×667

设备像素比(device pixel ratio/屏幕分辩比/dpr)

设备像素比 = 物理像素 / 设备独立像素

在某一方向上,x 方向或者 y 方向。在控制台通过 window.devicePixelRatio 可得。

所以 iphone6DPR 为 2。

scale

scale 是屏幕拉伸比。也就是视口上的 initial-scale , maximum-sacle 等属性。

视口(viewport)

  • 布局视口:网页在开始设计时候的 dom 宽度(比如 960px

  • 可视视口:整个屏幕的视口(比如 iphone6 375px

  • 完美视口:

    1
    2
    3
    4
    5
    6
    7
    8
    <meta name="viewport" content="initial-scale=1.0,width=device-width,user-scalable=0,maximum-scale=1.0" />
    <!--
    width:设置布局视口的宽
    init-scale:设置页面的初始缩放程度
    minimum-scale:设置了页面最小缩放程度
    maximum-scale:设置了页面最大缩放程度
    user-scalable:是否允许用户对页面进行缩放操作
    -->

目前主流的自适应布局解决方案

响应式(Responsive web design)

通过媒体查询根据不同的屏幕分辨率来进行适配。

优点:

  • media query 可以做到设备像素比的判断,方法简单,成本低,特别是对移动和 PC 维护同一套代码的时候。目前像 Bootstrap 等框架使用这种方式布局。
  • 图片便于修改,只需修改 css 文件。
  • 调整屏幕宽度的时候不用刷新页面即可响应式展示。

缺点:

  • 代码量比较大,维护不方便。
  • 为了兼顾大屏幕或高清设备,会造成其他设备资源浪费,特别是加载图片资源。
  • 为了兼顾移动端和 PC 端各自响应式的展示效果,难免会损失各自特有的交互方式。

流式布局

1
<meta name="viewport" content="width=width=device-width,initial-scale=1,maximum-scale=1, minimum-scale=1,user-scalable=no" />

流式布局需要用到百分比或者 flex,即宽度永远铺满页面宽度,但高度和其他单位仍然用 px。它的字体精度可以保持跟设备系统一致(dpi)。

移动端 vw+rem 布局(主流)

在没有 vw 的上古时代,我们通常这样来设置根字体 font-size 的大小:

1
2
3
4
5
6
7
8
(function () {
function autoRootFontSize() {
const width = Math.min(screen.width, document.documentElement.getBoundingClientRect().width);
document.documentElement.style.fontSize = width > 600 ? '16px' : (width / 750) * 32 + 'px'; //只适配移动端,且默认根字体为 32px,默认设计稿宽度 750px
}
window.addEventListener('resize', autoRootFontSize);
autoRootFontSize();
})();

通过屏幕尺寸发生变化就获取屏幕宽度的方式,动态获取计算根字体的大小。

如果支持 css vw,则 1vw 本质即等于上文中 width/100。则换算过来为 fontSize = (100vw/750) * 32,且不需要运行 js 及监听,直接设置 css 为:

1
2
3
4
5
html {
font-size: calc((100vw / 750) * 32);
} /* 以iphone6 750的设计稿为准 */

/* 其他元素用rem作为单位 */

然后启用 postcsspostcss-pxtorem 将其他元素的 px 转为 rem

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
plugins: {
autoprefixer: {},
'postcss-pxtorem': {
rootValue: 32,
propList: ['*'],
minPixelValue: 3,
mediaQuery: false,
},
},
};

其中 rootValue 为默认的根字体尺寸,通过 pxtorem 确定了其他元素尺寸根元素尺寸固定的比例对应关系。

scale 伸缩布局

视觉稿、页面宽度、viewport width 使用统一宽度,利用浏览器自身缩放完成适配。页面样式(包括图像元素)完全按照视觉稿的尺寸,使用定值单位 (px、em)即可完成。

法一

通过 js 更改 viewportinitial-scale

法二

写死 viewport 的宽度.

1
<meta name="viewport" content="width=360, user-scalable=no" />

优点:

  • 开发简单:缩放交给浏览器,完全按视觉稿切图。
  • 还原精准:绝对等比例缩放,可以精准还原视觉稿(不考虑清晰度的情况下)。
  • 测试方便:在 PC 端即可完成大部分测试,手机端只需酌情调整一些细节(比如图标、字体混合排列时,因为字体不同造成的对齐问题)。

缺点:

  • 像素丢失:对于一些分辨率较低的手机,可能设备像素还未达到指定的 viewport 宽度,此时屏幕的渲染可能就不准确了。比较常见的是边框“消失”了,不过随着手机硬件的更新,这个问题会越来越少的。
  • 缩放失效:某些安卓机不能正常的根据 meta 标签中 width 的值来缩放 viewport,需要配合 initial-scale
  • 文本折行:存在于缩放失效的机型中,某些手机为了便于文本的阅读,在文本到达 viewport 边缘(非元素容器的边缘)时即进行折行,而当 viewport 宽度被修正后,浏览器并没有正确的重绘,所以就发现文本没有占满整行。一些常用的段落性文本标签会存在该问题。
  • 不能开启 gpu raster 硬件加速:不能显式设置 minimum-scale=1.0,否则就达不到效果。而这个值是 chromium37 以上的 webview 触发 gpu raster 的一个条件,所以用这种方法就没法利用 gpu raster 硬件加速。

注:

其他

PX 适配

在新闻,社区等可阅读内容较多的场景直接使用 px 单位可以营造更好地体验。px 方案可以让大屏幕手机展示出更多的内容,更符合人们的阅读习惯。就无需 rem 来等比缩放。

  • 新闻,社区等可阅读内容较多的场景:px+flex+百分比
  • 对视觉组件种类较多,视觉设计对元素位置的相对关系依赖较强的移动端页面:vw + rem

retina 下图片高清方案

  1. 两倍图片(@2x),然后图片容器缩小 50%(方便但会造成资源浪费)。
  2. 不同的 dpr 下,加载不同的尺寸的图片。

retina 下 1px 解决方案

对于一条 1px 宽的直线,它们在屏幕上的物理尺寸是相同的,不同的其实是屏幕上最小的物理显示单元,即物理像素。所以对于一条直线,iphone6 它能显示的最小宽度用 css 来表示,理论上说是 0.5px。

所以,设计师想要的 retina 下 border: 1px;,其实就是 1 物理像素宽,对于 css 而言,可以认为是 border: 0.5px;,这是 retina 下(dpr=2)下能显示的最小单位。

元素 scale(方便但圆角等无法用)

1
2
3
4
5
6
7
8
9
10
11
12
13
.scale {
position: relative;
}
.scale:after {
content: '';
position: absolute;
bottom: 0px;
left: 0px;
right: 0px;
border-bottom: 1px solid #ddd;
-webkit-transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
}

判断添加 class

js 来判断当前浏览器是否支持 border 的 0.5px,如果支持就会在 HTML 上添加一个 classhairlines

1
2
3
4
5
6
7
8
9
if (window.devicePixelRatio && devicePixelRatio >= 2) {
var testElem = document.createElement('div');
testElem.style.border = '.5px solid transparent';
document.body.appendChild(testElem);
if (testElem.offsetHeight == 1) {
document.querySelector('html').classList.add('hairlines');
}
document.body.removeChild(testElem);
}
1
2
3
4
5
6
7
div {
border: 1px solid #bbb;
}

.hairlines div {
border-width: 0.5px;
}

注:3 倍屏下,不是 0.3333px?在 Chrome 上模拟 iPhone 8Plus,小于 0.46px 时无法显示。

设置 viewport 的 scale 值(不推荐)

1
2
3
4
5
6
7
8
9
10
11
var viewport = document.querySelector('meta[name=viewport]');
//下面是根据设备像素设置viewport
if (window.devicePixelRatio == 1) {
viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no');
}
if (window.devicePixelRatio == 2) {
viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no');
}
if (window.devicePixelRatio == 3) {
viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no');
}

因为涉及到所有的尺寸都要调整,且其他第三方组件不一定适配。

其他方式

  • 使用边框图片
  • 使用 box-shadow 实现: box-shadow: inset 0px -1px 1px -1px #d4d6d7;