h5开发(3)-px、viewport与rem方案

1,752 阅读6分钟

实习期间做h5开发,有两个问题一直很困扰:

  • 代码中几乎都会使用<meta name="viewport" content="width=device-width">这个标签,一直都没有搞得太清楚为什么这个标签可以使得我们的代码适应移动端?
  • ui小姐姐每次都是给750px的设计稿,我应该怎么写css才能使不同型号的手机显示的都与设计稿一致?

为了建立起一个比较系统的概念,先从像素开始学习。

物理像素

通俗来说,物理像素就是手机屏幕上的一个个小晶格。比如两个实际尺寸(英寸)相同的手机屏幕,一个分辨率是375,一个是750,则一个相当于把手机一行平均分成375个小格子,一个平均分为750个小格子。所以分辨率越高,物理像素意义上的1px看起来越小。

说到物理像素,有个紧密相关的概念就是PPI.PPI 全称为 Pixel Per Inch,译为每英寸像素取值,更确切的说法应该是像素密度,也就是衡量单位物理面积内拥有物理像素值的情况。

css像素

对于我们web开发人员而言,接触更多的是css像素。首先需要明确的一点是,css像素是一个相对值。最开始时,1px的css像素默认等于1px的物理像素。但随着手机屏幕分辨率的提升,1px的物理像素变得特别小,导致我们写的同一份css代码里的1px,在低分辨率的手机里看着还可以,但在高分辨率的手机里就小的没有办法看。
为了解决这个问题,苹果提出了retina技术,即使用4个物理像素来渲染一个css像素。

因为存在了一个换算关系,就出现了dpr这个概念。 DevicePixelRatio 定义如下: window.devicePixelRatio = physical pixels / dips
分母 dips 全称为 device-independent pixels,译为与设备无关像素
更通俗的说应为与物理像素无关的 CSS 像素。
如上图所示,对于css:width=2px, 标准屏水平方向上有2个物理像素,所以dpr=2/2=1,而retina屏水平上有4个物理像素,所以dpr=4/2=2

注意: retina的这种换算关系是手机的操作系统帮我们换算好了,从我们开发人员的角度来说,写的一份代码width=2px应用到不同型号手机上显示效果都是一致的,所以对于一般的css样式,我们无需关注物理像素而只需要关注css像素即可

而对于图片资源,图片的分辨率是物理像素,所以当一个375 * 375的图片应用到retina屏是就会被拉伸为750 * 750,会造成图片的失真,所以苹果鼓励开发者准备两份素材,普通和高清素材。并且通过素材文件名后缀来区分,比如普通素材名称为 apple.png,那么高清素材名称就为apple@2x.png 。自然高清素材是普通素材面积的四倍,系统会优先使用高清素材,但自动缩小到普通素材的大小,这样也就不存在图片拉伸的问题了。

viewport

说完像素值的问题,接下来则是viewport。对于pc端的页面而言,viewport的大小与浏览器窗口一致。
而在移动端,浏览器默认是以980px来渲染(我理解的此处的px为css像素),这就会造成一个问题,比如css样式规定字体大小为16px,

  • 假设pc端的浏览器分辨率为1400px,宽度为13英尺,则字体看起来的实际尺寸约为 13*(16/1400)=0.15inch
  • 假设手机的宽度为4英尺,浏览器分辨率为980px,则字体看起的实际尺寸约为4*(16/980)=0.06inch,会变得特别小。
<!DOCTYPE html>
<html>
    <head>
        <style>
            * {
                margin: 0;
                padding: 0;
            }
        </style>
    </head>
    <body>
        <div style="width: 980px; height: 50px; background: yellow;"></div>
        <p style="font-size: 16px">测试测试测试</p>
    </body>
</html>

pc端的效果:

看起来还是正常的

移动端的效果:

字体很小难以看清

所以这个时候,我们就要用meta标签来指定视口的宽度,将980px强行转换成更小的分辨率。

meta

我们最常用的meta如下:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
首先先来试一下,将width设为数字时,

<!DOCTYPE html>
<html>
    <head>
    <meta name="viewport" content="width=400, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
        <style>
            * {
                margin: 0;
                padding: 0;
            }
        </style>
    </head>
    <body>
        <div style="width: 200px; height: 50px; background: yellow;"></div>
    </body>
</html>

pc端效果:

iphone6 plus (dpr=3)效果:

iphone6 (dpr=2)效果:
可以看出,meta设置viewport对于pc端页面无效,对于移动端,dpr=2/dpr=3时都是占据了页面的一半,所以我理解的是,这里设置的400是css像素,至于具体用了多少个物理像素来表示1个css像素,手机自身会转换,我们无需关注。
而设置成device-width就是手机操作系统转换好的最优的css像素,对于iphone6来说就是375px,对于6 plus来说就是414px。

我的理解

这条meta标签主要是两个功能:

  • 将手机浏览器的viewport设置成手机屏幕的大小
  • 将不同物理像素的手机的viewport统一为同一概念的css像素

750px的设计稿怎么来开发-rem方案

我们的目的是,一份代码,一份设计稿,可以适配各种分辨率的手机。
假设我们拿到一个宽750px的psd设计稿,里面有个宽375px的盒子,我们希望只需要一份代码,在不同型号的手机屏幕里都可以看到,盒子占手机屏幕的一半。

  • 写死可不可以?

    肯定不行,通过前面分析可知,不同手机的viewport的宽度不一样,如果写死,如写成375px,则在iphone6中会占满,在6 plus中占90%左右,行为不一致。

  • 不变的是相对百分比

    (1)我们将750px的设计稿等分为10份,每份75px,设置基底base=75px,则375px/75px = 5,即5rem。对于不同的手机型号而言,5这个倍数都是一样的,变化的是基底base
    (2)我们通过js获取到手机的viewport,同样分为10份,设置为基底。

    iphone6的base=37.5px,盒子宽度5rem = 5 * 37.5px,正好是视口的一半;
    6 plus的base=41.4px,盒子宽度5rem = 5 * 41.4px,也正好是视口的一半

所以开发过程中,通过js动态设置根元素的大小,然后就照着设计稿量出长度,然后除以75px,单位为rem即可

设置基底的代码

    function getScale(originFs) {
        var dom = document.createElement('div');
        var body = document.body;
        dom.style.fontSize = originFs + 'px';
        body.appendChild(dom);

        var realFs = window.getComputedStyle(dom, null).getPropertyValue('font-size');
        realFs = parseFloat(realFs);
        body.removeChild(dom);

        return originFs / realFs;
    }

    function initRem() {
        var ele = document.documentElement;
        var w = ele.clientWidth || 320;
        var originFs = w / 10;
        ele.style.fontSize = originFs + 'px';
        
        setTimeout(function () {
            var scale = getScale(originFs); // 干预浏览器缩放产生的影响
            originFs = originFs * scale;
            ele.style.fontSize = originFs + 'px';
        }, 0);
    }