移动适配

基础概念

像素

设备像素

device pixels,简称 dp,物理像素,出厂就固定不变

设备独立像素

在不缩放的情况下,css 中的 1px 就代表着 1 个独立像素
早期移动设备,没有独立像素,css 中的 1px 就代表着 1 个物理像素,后面设备出现了独立像素,,但是 1 个独立像素不一定对应 1 个物理像素,需要根据设备像素比转换

设备像素比

device pixel ratio,简称 dpr,的设备像素 / 设备独立像素 = 设备像素比

  • 当 dpr = 1 时,使用 1(1 * 1) 个设备像素显示 1 个 css 像素
  • 当 dpr = 2 时,使用 4(2 * 2) 个设备像素显示 1 个 css 像素
  • 当 dpr = 3 时,使用 9(3 * 3) 个设备像素显示 1 个 css 像素

css 像素

css 和 js 中使用的 px
当缩放页面的时候,元素的 css 像素不会改变,改变的只是 css 像素的 如,width:100px 的元素缩放 200% 之后,宽度仍然为 100 个像素,但是每个像素的大小变成了原来的 2 倍,此时对应的设备独立像素就为 200 个了

视口

布局视口

layout viewport,在 pc 端,等于浏览器窗口的宽度,可通过 document.documentElement.clientWidth || document.body.clientWidth 来获取

视觉视口

visual viewport,用户正在看到的网页区域,可通过 window.innerWidth 来获取

理想视口

ideal viewport,布局视口的一个理想尺寸,只有当布局视口的尺寸等于设备屏幕的尺寸时,才是理想视口,可通过 window.screen.width 来获取

视口总结

  • 在桌面浏览器上,浏览器窗口与视口的宽度一致,而视口(CSS 标准文档中称为“初始包含块”)是 CSS 百分比宽度推算的根源,因此,浏览器窗口是约束 CSS 布局的视口
  • 在手机上,有两个视口,布局视口会限制 CSS 布局;视觉视口决定用户看到的网站内容
  • 移动端浏览器还有个理想视口,它是对特定设备上的特定浏览器的布局视口的一个理想尺寸
  • 可以把布局视口尺寸定义为理想视口。这也是响应式设计的基础
  • <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
参数说明
width设置 layout viewport 的宽度,一个正整数,或字符串 “device-width”
height设置 layout viewport 的高度,一般很少使用
initial-scale设置页面的初始缩放值,为一个数字,可以带小数,相对于理想视口进行缩放,即 width=device-width 与 initial-scale=1 是相同的效果,但都有瑕疵,因此两个都写上
minimum-scale允许用户的最小缩放值,为一个数字,可以带小数
maximum-scale允许用户的最大缩放值,为一个数字,可以带小数
user-scalable是否允许用户进行缩放,值为“no”或“yes”

适配方案

方案 1:固定高度,宽度自适应

优点:使用较多,相对简单
缺点:还原度较低
使用方法:单纯设置 meta 属性,元素垂直方向使用固定的值,水平方向采用弹性布局

方案 2:固定布局视口宽度,使用 viewport 进行缩放

优点:
缺点:
使用方法:
方法 1:

if (/Android (\d+\.\d+)/.test(navigator.userAgent)) {
    var version = parseFloat(RegExp.$1);
    if (version > 2.3) {
        var phoneScale = parseInt(window.screen.width) / 640;
        if (/MZ-M571C/.test(navigator.userAgent)) {
            document.write(
                '<meta name="viewport" content="width=640, minimum-scale=0.5, maximum-scale=0.5">'
            );
        } else if (
            /M571C/.test(navigator.userAgent) &&
            /LizhiFM/.test(navigator.userAgent)
        ) {
            document.write(
                '<meta name="viewport" content="width=640, minimum-scale=0.5, maximum-scale=0.5">'
            );
        } else {
            document.write(
                '<meta name="viewport" content="width=640, minimum-scale=' +
                    phoneScale +
                    ', maximum-scale=' +
                    phoneScale +
                    ', target-densitydpi=device-dpi">'
            );
        }
    } else {
        document.write(
            '<meta name="viewport" content="width=640, target-densitydpi=device-dpi">'
        );
    }
} else {
    document.write(
        '<meta name="viewport" content="width=640, user-scalable=no, target-densitydpi=device-dpi">'
    );
}

方法 2:未细看该代码

var win = window,
    width = 640,
    iw = win.innerWidth || width,
    ow = win.outerHeight || iw,
    sw = win.screen.width || iw,
    saw = win.screen.availWidth || iw,
    ih = win.innerHeight || width,
    oh = win.outerHeight || ih,
    ish = win.screen.height || ih,
    sah = win.screen.availHeight || ih,
    w = Math.min(iw, ow, sw, saw, ih, oh, ish, sah),
    ratio = w / width,
    dpr = win.devicePixelRatio;
if (((ratio = Math.min(ratio, dpr)), 1 > ratio)) {
    var ctt = ',initial-scale=' + ratio + ',maximum-scale=' + ratio,
        metas = document.getElementsByTagName('meta');
    ctt += '';
    for (var i = 0, meta; i < metas.length; i++)
        (meta = metas[i]), 'viewport' == meta.name && (meta.content += ctt);
}

固定布局视口,宽度设置固定的值,总宽度为 640px,根据屏幕宽度动态生成 viewport <meta name="viewport" content="width=640, minimum-scale=0.5625, maximum-scale=0.5625, target-densitydpi=device-dpi">

方案 3:固定布局视口宽度,以 rem 作为宽度单位,根据不同屏幕动态写入 font-size

优点:还原度较高
缺点:
使用方法:以 640px 设计稿和 750px 的视觉稿

var width = document.documentElement.clientWidth; // 屏幕的布局视口宽度
var rem = width / 7.5; // 750px设计稿将布局视口分为7.5份
var rem = width / 6.4; // 640px设计稿将布局视口分为6.4份

这样不管是 750px 设计稿还是 640px 设计稿,1rem 等于设计稿上的 100px。故 px 转换 rem 时:1 px = rem * 0.01
在 750px 设计稿上,75px 对应 0.75rem,距离占设计稿的 10%

在 iphone6 上
width = document.documentElement.clientWidth = 375px;
1rem = 375px / 7.5 = 50px;
0.75rem = 37.5px;(37.5 / 375 = 10%,占屏幕 10%)
在 iphone5 上
width = document.documentElement.clientWidth = 320px;
1rem = 320px / 7.5 = 42.667px;
0.75rem = 32px;(32 / 320 = 10%,占屏幕 10%)

方案 4:以 rem 作为宽度单位,设置默认 dpr,得到默认的 rem 值,动态写入 viewport 和 font-size 进行缩放

该方案有默认的 rem 值,方案 5 没有

使用方法:根据设置的 dpr 设置 font-size

  • document.documentElement.style.fontSize = 50 * dpr;
  • dpr 为设置的设备像素比(注意不是设备自身的设备像素比,而是人为设置的 dpr)
  • 此时,若 dpr = 1,则 1rem = 50px,dpr = 2,则 1rem = 100px
  • 当设计师以 iphone6 为标准,出 750px 的设计稿时,此时 dpr = 2,故 1rem = 100px,将图上的尺寸转为 rem 只需除于 100 即可
var scale = 1.0;
var dpr = 1;
var isAndroid = window.navigator.appVersion.match(/android/gi);
var isIPhone = window.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = window.devicePixelRatio;
// 此处只简单对ios做了伸缩处理,安卓没有做伸缩处理,统一 dpr = 1
if (isIPhone) {
    scale /= devicePixelRatio;
    dpr *= devicePixelRatio;
}
var viewport = document.getElementById('viewport');
var content =
    'initial-scale=' +
    scale +
    ',maximum-scale=' +
    scale +
    ',minimum-scale=' +
    scale +
    ', width=device-width, user-scalable=no';
viewport.setAttribute('content', content);
document.documentElement.style.fontSize = 50 * dpr + 'px';
document.documentElement.setAttribute('data-dpr', dpr); // 留作 css hack 用

假设肉眼看到的宽度(视觉宽度):visualWidth,令 dpr=1 时,其 1rem 对应的宽度为 50

  • dpr = 1 时, 1rem = 50px, initial-scale=1, 缩放为 1,visualWidth = 50 * 1 = 50
  • dpr = 2 时, 1rem = 100px, initial-scale=0.5, 缩放为 0.5,visualWidth = 100 * 0.5 = 50
  • dpr = 3 时, 1rem = 150px, initial-scale=0.3333, 缩放为 0.3333,visualWidth = 150 * 0.3333 = 50

方案 5:以 rem 作为宽度单位,将屏幕分为固定单位,动态写入 font-size 和 viewport

rem 无默认值,方案 4 的有

优点:还原度较高

使用方法:将屏幕分为固定的块数 10

var width = document.documentElement.clientWidth;
var rem = width / 10;

// 此时,在任何屏幕下,总长度都为 10rem,1rem 对应的值不固定,与屏幕的布局视口宽度有关
// 动态生成 viewport,对于动态生成 viewport,他们原理差不多,根据 dpr 来设置缩放

var devicePixelRatio = width.devicePixelRatio;
var isIPhone = document.navigator.appVersion.match(/iphone/gi);
var dpr, scale;
if (isIPhone) {
    if (devicePixelRatio >= 3) {
        dpr = 3;
    } else if (devicePixelRatio >= 2) {
        dpr = 2;
    } else {
        dpr = 1;
    }
} else {
    dpr = 1;
}
scale = 1 / dpr;

方案小结

  • 方案 3 与 方案 5,使用较方便,也容易理解,两者也比较相似
  • 方案 2 做了百分比适配,部分 1px 适配,没有字体适配
  • 方案 3 做了百分比适配,没有 1px 适配,有字体大小适配
  • 方案 4 没有做百分百适配,布局要用百分百和 flex 布局,做了 1px 的适配,并且对于段落文字直接可以用 rem 做单位,不需要做适配
  • 方案 5 做了百分比适配,有 1px 适配,有字体大小适配

适配总结

移动端适配最主要的是使在不同屏幕下不用缩放页面就能正常显示整个页面,其次才考虑以下需求:

  • 解决高清屏下 1px 的问题,其实有很多 hack 方法,这里只讲了缩放视口。先将布局视口设置为高清屏的物理像素。这样 css 中 1px 就是 1 个物理像素,这样看到的线条才是真正的 1px。但是此时视口宽度大于设备的宽度,就会出现滚动条。故对视口进行缩放,使视口宽度缩放到设备宽度。
  • 在大屏手机中一行看到的段落文字应该比小屏手机的多,所以段落文本一般使用 px 作为单位,对于不同 dpr 设置不同的大小(方案 4 的 1rem 不管何时,视觉上的宽度都是一样的,所以可以用 rem 作为字体单位)
.selector {
    color: red;
    font-size: 14px;
}
[data-dpr='2'] .selector {
    font-size: 28px; // 14 * 2
}
[data-dpr='3'] .selector {
    font-size: 42px; // 14 * 3
}
Last Updated:
Contributors: zhangfei