网易和淘宝移动 WEB 适配方案再分析

7,794 阅读12分钟

最近把移动WEB适配相关的问题梳理了一遍,学习了几篇文章,其中

从网易与淘宝的font-size思考前端设计稿与工作流 - 流云诸葛 - 博客园

分析了网易和淘宝对移动WEB适配问题的解决方案,干货不少,但是一些概念和思路不是很清晰。我在这里结合一些其他的文章和自己的实验思考对两种适配方案再做分析,顺便把相关的知识点做个总结。

移动适配中涉及到的一些基本概念和原理参考文章

viewports剖析_viewports 教程_w3cplus

移动端适配方案(上) - WEB前端 - 伯乐在线

在这里提取一些比较重要的规则

  1. 布局视口(layout viewport)可以看作是html元素的上一级容器即顶级容器,默认情况或者将html元素的width属性设为100%时,会占满这个顶级容器,此时用
    document.documentElement.clientWidth 
    
    获取到html元素的布局宽度也就是布局视口的宽度,使用媒体查询时 max-width 和 min-width 的值指的也是布局视口的宽
  2. 布局视口的宽度和高度单位是像素px,这个单位是CSS和JS中使用的抽象单位,为了便于区分称作CSS像素。布局视口的宽度有一个默认值,一般在 768px ~ 1024px 之间,最常见的宽度是 980px,在这个默认值下进行布局得到的页面比较接近PC端布局的效果
  3. 可视视口(visual viewport)是指用户可见页面区域,其宽度值为横向可见CSS像素数,从另一个角度理解,可视视口的宽度决定了将屏幕横向分为多少份,每份对应一个CSS像素,使用
    window.innerWidth
    
    可以获取到可视视口的宽度
  4. 理想视口(ideal viewport)是一个比较适合页面布局使用的视口尺寸,作为计算布局视口和可视视口尺寸时的一个基准值,下面代码中 device-width 的值就是理想视口的宽度
    <meta name="viewport" content="width=device-width">
    
    使用
    screen.width
    
    也可以获取到理想视口的宽度
  5. 三个视口中只有理想视口的尺寸是不能改变的,由设备和浏览器决定,与设备的物理像素数存在着比例关系,这个比例就是设备像素比(device pixel ratio, dpr),即有 设备像素比 = 物理像素数 / 理想视口尺寸,举例iphone5屏幕横向有640个物理像素,其理想视口宽为320,则 dpr=2,可以使用
    window.devicePixelRatio
    
    获得dpr,但在安卓系统下这个值可能不符合预期
  6. 可视视口的尺寸收到缩放比例的影响,因此用户手动缩放和在 meta 标签中设置 initial-scale 的值都会改变可视视口的尺寸,可视视口的尺寸越小即显示的CSS像素数越少,则单位CSS像素对应的可使区域越大,对应的缩放比也就越大。缩放比例是相对于理想视口而言的,即有 缩放比例 = 理想视口尺寸 / 可视视口尺寸。对iphone5,设置
    <meta name="viewport" content="initial-scale=0.5">
    
    则其可视视口尺寸为640个CSS像素
    缩放比例也有默认的值,没有设置 initial-scale 时,浏览器会取适当的缩放比例使 布局视口正好铺满屏幕即有 布局视口尺寸 = 可视视口尺寸
  7. 布局视口的宽度受到 meta 标签中的 width 和 initial-scale 的影响
    仅设置 width 的值时,这个值就是布局视口的宽度,width的值可以为正整数或特殊值 device-width
    <meta name="viewport" content="width=400">
    
    此时由于没有禁用缩放,一般可以通过双击屏幕对页面进行缩放,但手动缩放不会影响布局适口的尺寸,只会影响到可视视口的尺寸,而且可能导致布局视口小于可视视口
    设置 initial-scale 的值时,布局视口的尺寸与可视视口的计算方式相同,但不受手动缩放的影响
    同时设置 width 和 intial-scale 的值时,布局视口的宽取上述两个值中较大的一个
  8. 布局视口宽 = 可视视口宽时 html 元素正好横向铺满窗口(但其后代元素若有横向 overflow 的情况,仍然会出现滚动条),布局视口宽 > 可视视口宽时,出现横向滚动条

由上述规则可以得到一些有用的结论
  1. 将 meta 标签中的 width 设为 device-width 同时禁用手动缩放可以使 布局视口尺寸 = 可视视口尺寸 = 理想视口尺寸,此时 设备像素比 = 物理像素数 / 理想视口尺寸 = 物理像素数 / 布局视口尺寸,对iphone5,一个CSS像素对应4个物理像素
  2. 为 initial-scale 设置任意合法的值同时禁用手动缩放就可以使布局视口尺寸 = 可视视口尺寸
  3. 将 initial-scale 设置为 1 也可以使 布局视口尺寸 = 可视视口尺寸 = 理想视口尺寸

下面具体分析网易和淘宝的适配方案

其实两种方案在处理元素尺寸的适配时采用了相同的思路,即使用相对单位 rem 并将设备的可视视口宽度乘以一个系数得到 html 元素的 font-size,元素布局时不超出可视视口宽度即可

实际上我们并不需要特别设置布局视口的宽度——只要所有需要展示的元素填满可视视口即可,理论上如果设置了 meta 中的 width 值且大于可视视口的计算结果,会出现横向滚动条,但网易和淘宝的方案都没有设置 width,此时布局视口的宽等于可视视口,只要没有超出可视视口宽度的元素,就不会出现滚动条

先分析一下网易新闻的方案

  1. 第一个要解决的关键问题是如何为设备选择可视视口尺寸,网易新闻的方案相对简单,采用理想视口尺寸作为可视视口尺寸,代码也十分简单,只需要将缩放比定为 1
    <meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">
    
  2. 第二个要解决的问题是计算 html 元素的 font-size,要将可视视口的宽度乘以一个系数
    理论上这个系数可以是任意值,假设将这个系数取 1,则 html 元素的 font-size 即1 rem等于可视视口的宽度,此时以 rem 为单位的长度 n rem 就可以理解为 n 倍可视视口的长,这个系数取 0.01 时,1 rem 等于可视视口宽的 1/100,也就等于布局视口宽的 1/100,也就等于 1vw。实际使用过程中这个系数的选择尽可能方便将设计稿长度数值换算为css中的长度数值

    网易新闻手机网易网选择的系数为 100 / 750,这个系数可以如下推出:

    750px 是设计稿的宽度(以iphone6的物理像素数为标准),100是期望的换算比例,即设计稿中 100px 的长度对应css中 1rem,将设计稿中的长度数值除以 100 得到的就是以 rem 为单位的 css 长度的数值,设计稿的宽换算为以 rem 为单位的 css 长度应为 (750/100) rem,同时设计稿的宽对应可视视口的宽,即有 (750/100) rem = 可视视口宽,1 rem = 可视视口宽 * (100/750),(100/750)就是我们要的系数

    在页面初始化时设置一下 html 元素的 font-size

    document.documentElement.style.fontSize = window.innerWidth / 7.5 + 'px';
    
  3. 第三个要解决的是将可视视口的宽度换算为 rem 单位的数值,布局时元素不能超出这个值由 (750/100) * font-size = 可视视口宽 可知 可视视口宽为 (750/100) rem
    网易新闻的解决方案中将这个值设置到了 body 元素的 width 上但并没有对 overflow 进行处理,所以实际上没有起到什么作用
    输出的html如下(iphone6,device-width = 375,dpr = 2)
    <html style="font-size: 50px;">
    ...
    <meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">
    ...
    
  4. 在最新的网易新闻页面中字体的单位也使用了 rem
然后对比地分析一下淘宝的方案
  1. 在为设备选择合适的可视视口时淘宝的方案显得复杂,但是有其巧妙之处,在他们的开源项目使用Flexible实现手淘H5页面的终端适配 · Issue #17 · amfe/article中提到了缩放比的算法

  2. if (!dpr && !scale) {
        var isAndroid = win.navigator.appVersion.match(/android/gi);
        var isIPhone = win.navigator.appVersion.match(/iphone/gi);
        var devicePixelRatio = win.devicePixelRatio;
        if (isIPhone) {
            // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
            if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {                
                dpr = 3;
            } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
                dpr = 2;
            } else {
                dpr = 1;
            }
        } else {
            // 其他设备下,仍旧使用1倍的方案
            dpr = 1;
        }
        scale = 1 / dpr;
    }
    

    概括一下,就是取 dpr 的倒数作为缩放比例,对 iOS 设备 dpr = window.devicePixelRatio ,其他设备认为 dpr 为 1
    对 iOS 设备,令上面提到的公式 缩放比例 = 理想视口尺寸 / 可视视口尺寸,设备像素比 = 物理像素数 / 理想视口尺寸 中 设备缩放比 = 1 / 缩放比例 可以推出 可视视口尺寸 = 物理像素数,同时由于没有设置 meta 标签的 width 值,有 布局视口尺寸 = 可视视口尺寸 = 物理像素数,这意味着布局视口中的像素单位是和物理像素一一对应的,css单位中1px严格等于一个物理像素。这就是淘宝方案的巧妙之处了,对于 iOS 下高分辨率的设备,提供了更好的支持,解决了 1px border 问题和高清图片的问题,详细的解释见 移动端高清、多屏适配方案 - Div.IO
    对非 iOS 设备,将 dpr 设为 1,缩放比例也为 1,和网易新闻的方案相同。

  3. 在计算 html 的 font-size 时淘宝的方案直接将可视视口的宽度乘以一个系数 0.1,由此推导换算比例:
  4. 可视视口的宽度为 10 rem, 对应设计稿的宽度,则 1 rem 对应设计稿宽度的 1 / 10,换算时将设计稿中的长度数值除以 (设计稿宽度/10) 就可以了。
    例如:设计稿的宽度为 750 则设计稿中的长度数值除以 75 得到的就是以 rem 为单位的 css 长度的数值

    在 meta 标签中的缩放比例发生变化时,设置 html 元素的 font-size

    document.documentElement.style.fontSize = window.innerWidth / 10 + 'px';
    
  5. 由于可视视口的宽度就是 10 rem,对元素进行布局时只要不超过 10rem 即可,另外淘宝的方案将 body 的 width 设为 100% 并对 overflow 进行 hidden,这个 100% width 会计算为布局视口的宽,也是 10rem,那么 body 内布局超出 10rem 的元素不会导致布局视口出现滚动条
  6. 淘宝的方案中对字体使用了 px 单位,这就需要对不同的视口宽度(也就是不同dpr)的设备分别进行适配,淘宝的方案是将 dpr 数值设置为 html 的 data-dpr 属性,通过css选择器选择不同 dpr 设备下的元素
    div {
        width: 1rem; 
        height: 0.4rem;
        font-size: 12px; // 默认写上dpr为1的fontSize
    }
    [data-dpr="2"] div {
        font-size: 24px;
    }
    [data-dpr="3"] div {
        font-size: 36px;
    }
    

对比总结
从可视视口的选择可以看出 淘宝方案对 iOS 高分辨率的设备提供了特别的支持,而对其他设备一律采用了精确度较低的处理方式,网易新闻则对所有设备都采用了精确度较低的处理方式。在这一点上,淘宝方案可看作是网易方案的增强版,而在 dpr = 1 的设备下,淘宝方案可以退化为网易方案。淘宝方案还可以使用 px 单位对字体进行精确度比较高的适配,而对于网易方案,使用 px 作为字体的单位意义不大。

在 html 元素 font-size 的计算上,网易方案选择了一个比较人性化的换算比例,根据这个比例去推算 font-size 的系数和对应的视口尺寸的 rem 值,淘宝方案反过来将可视视口定为 10rem和 font-size 的系数,由此推算换算比例。比较而言网易方案进行人工换算和检查时更容易些,淘宝方案虽然可以借助工具完成自动换算,但在阅读输出的 css 样式时就有点费力了。

实际运用中,可以将两种方案的优点结合起来,形成更好的适配方案。

其他方案

这篇文章移动端适配方案(下) - WEB前端 - 伯乐在线中提到的第二种方案是另外一种思路,将布局视口尺寸固定,然后通过缩放使可视视口尺寸等于布局视口尺寸,进行元素布局时按照固定好的布局视口。原理用动图展示最为直观:

文中给出的实现代码如下:

<meta name="viewport" content="width=640,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">

缩放比例根据理想视口计算的,缩放比例 = 理想视口尺寸 / 可视视口尺寸,使 可视视口尺寸 = 布局视口尺寸 ,则 缩放比例 = 理想视口尺寸 / 布局视口尺寸

布局视口的尺寸取设计稿的尺寸时,css像素对应设计稿中的 px,不需要进行单位换算

文中给出了两个例子金币商城荔枝FM,人人都是播客_听音乐相声评书脱口秀鬼故事广播剧网络电台,但查看两个例子的代码发现其实现方式是另一种

<meta content="target-densitydpi=device-dpi,width=640,user-scalable=no" name="viewport">

测试发现这种方式也可以使 可视视口尺寸 = 布局视口尺寸 ,但有资料显示target-densitydpi是一个将被抛弃的属性,因此不推荐使用