【响应式布局】理解设备像素、设备独立像素和css像素

这篇文章是我在我的旧博客上发过的文章,最近又碰到这个问题,整理修改了一下发到这里。

像素单位

像素单位有设备像素、逻辑像素和CSS像素3种。

设备像素(device pixels)、设备分辨率

设备像素也叫物理像素。

设备像素指的是显示器上的真实像素,每个像素的大小是屏幕固有的属性,屏幕出厂以后就不会改变了。
设备分辨率描述的就是这个显示器的宽和高分别是多少个设备像素。
设备像素和设备分辨率交给操作系统来管理,浏览器不知道、也不需要知道设备分辨率的大小,浏览器只需要知道逻辑分辨率就可以了。

设备独立像素(Device Independent Pixels)、逻辑分辨率

设备独立像素也叫逻辑像素。

设备独立像素(dips)是操作系统为了方便开发者而提供的一种抽象。应用程序与操作系统之间描述长度时以设备独立像素为单位,然后操作系统再将单位从设备独立像素转化为设备像素,从而控制屏幕上真正的物理像素点。

为什么需要在应用程序与设备像素之间定义这么一种单位呢?为什么应用程序不应该直接使用设备像素来描述长度?
随着显示器制造技术越来越先进,屏幕像素密度越来越高。同样是1920*1080颗像素,以前要放在宽大的显示器中,现在都可以放在手机屏幕上了。原本高度为12个设备像素的字体,现在高度为24个设备像素才能得到相近的大小(这也说明字变得更加清晰锐利了),如果应用程序直接使用设备像素,那么编写应用程序将变得非常困难:字体在一些屏幕上高度为12个设备像素,在另一些屏幕上却要变为24个设备像素。
因此操作系统定义了一个单位:设备独立像素。操作系统保证:用设备独立像素定义的尺寸,不管屏幕的参数如何,都能以合适的大小显示(这也是设备独立像素名字的由来)。操作系统是如何做到的呢?对于那些像素密度高的屏幕,将多个设备像素划分为一个逻辑像素。至于将多少设备像素划分为一个逻辑像素,这由操作系统决定
对于上面的例子:“原本高度为12个设备像素的字体,现在高度为24个设备像素才能得到相同的大小”,操作系统会将一个逻辑像素定义为2*2个真实像素,从而设备独立像素尺寸不需要改变,而且不管在新、旧设备上,显示的尺寸大致相同。
【响应式布局】理解设备像素、设备独立像素和css像素

设备独立像素与设备像素之间的比例是多少,显示器厂商和操作系统厂商会通过调查研究来得出最利于观看的比例。普遍规律是,屏幕的像素密度越高,就需要更多的设备像素来显示一个设备独立像素。

通过screen.width/height得到的数值就是整个屏幕(不仅仅是浏览器的区域)的宽度和高度(单位:设备独立像素)。这个数值不随页面缩放、浏览器窗口大小而改变。

逻辑分辨率用屏幕的宽*高来表示(单位:设备独立像素)。

通过操作系统设置来改变设备独立像素的大小

你可以通过操作系统的分辨率设置来改变设备独立像素的大小,但在前端开发的时候我们完全可以将它们当作定值。(没人会闲着无聊频繁改变操作系统分辨率)
【响应式布局】理解设备像素、设备独立像素和css像素
我屏幕的设备分辨率是1920*1200(单位:设备像素),当前的分辨率设置下逻辑分辨率是1280*800(单位:设备独立像素)。从图中可以验证,横、纵方向的设备像素数量恰好是设备独立像素的1.5倍。这也意味着,设备独立像素的边长是设备像素边长的1.5倍。

window.devicePixelRatio在下文会解释。

css像素

在CSS中使用的px都是指css像素,比如width: 128px。css像素的大小是很容易变化的。当我们缩放页面的时候,元素的css像素数量不会改变,改变的只是每个css像素的大小。也就是说width: 128px的元素在缩放200%以后,宽度依然是128个css像素,只不过每个css像素的宽度和高度变为原来的两倍。如果原本元素宽度为128个设备独立像素,那么缩放200%以后元素宽度为256个设备独立像素(css像素宽度始终是128)。

开发者在开发的时候基本上只用考虑css像素,在这里介绍设备像素和设备独立像素只是为了讲述页面缩放的原理,以及方便以后理解viewport。

css像素与设备独立像素的关系

缩放比例就是css像素边长/设备独立像素边长
根据缩放比例和设备独立像素边长,就能计算出css像素边长。
缩放比例为100%的情况下,一个css像素大小等于一个设备独立像素。

window.devicePixelRatio

window.devicePixelRatio设备像素比。
devicePixelRatio = CSS像素边长/设备像素边长。比如devicePixelRatio=2,表示CSS像素的边长是设备像素的2倍,因此在相同长度的直线上,设备像素的数量是CSS像素数量的2倍,需要4个设备像素来显示1个CSS像素。

在桌面浏览器上,缩放会导致CSS像素边长的改变,从而导致window.devicePixelRatio的改变!

但是!当移动端浏览器加入讨论时,事情就变得复杂了一些。事实上,桌面浏览器与移动端浏览器的缩放机制是不一样的!桌面浏览器的是page zoom,移动端浏览器的是pinch zoom。
根据CSS标准,计算window.devicePixelRatio时,不考虑pinch zoom对CSS像素尺寸的影响。因此,在移动端缩放不会造成window.devicePixelRatio的改变。通过chrome的远程调试就能测试出这一点。

【响应式布局】理解设备像素、设备独立像素和css像素
【响应式布局】理解设备像素、设备独立像素和css像素
以上2张图展示了我在移动端浏览器上缩放前后,分别获取到的devicePixelRatio值,可以看出它不受缩放的影响。

page zoom和pinch zoom的主要区别是,前者会造成layout viewport的尺寸(以CSS像素为单位)改变而后者不会。
为什么要弄两套缩放机制?这是因为,在桌面端放大时,用户不希望出现横向滚动条,因此需要将layout viewport(网页布局宽度)始终保持与visual viewport(浏览器窗口可视宽度)相等。在缩放时,它们的物理尺寸是不变的(比如,以厘米为单位或者以设备像素为单位),但是CSS像素的尺寸会随着缩放变大,结果就是网页的layout/visual viewport(以CSS像素为单位)变小了(一行只能显示更少的字)。而在移动端,手机的屏幕非常窄,如果使用同样的机制,用户一放大,网页的layout viewport变小(比如小到只有200CSS像素),布局就会非常恶心。因此,在移动端放大时,浏览器不再干涉layout viewport,让它随着CSS像素一起“膨胀”,layout viewport的宽度(以CSS像素为单位)不变,但是visual viewport的宽度(以CSS像素为单位)变小,因此会出现横向滚动条。(你会发现,在手机上放大网页,一行能够显示的字的数量不变,只不过需要横向滚动才能看完一整行)
ppk大神在他的文章中也解释了page zoom和pinch zoom。

另外请注意,改变操作系统分辨率会导致设备独立像素边长改变,从而导致CSS像素边长改变(前面已经说过,CSS像素边长是根据缩放比例和设备独立像素计算出来的),从而导致window.devicePixelRatio的改变!网上很多文章不区分设备独立像素和设备像素,那样就无法解释这个现象。

例子

【响应式布局】理解设备像素、设备独立像素和css像素
我的屏幕宽度是1280个设备独立像素。这个值可以直接通过window.screen.width获得,或者自己根据操作系统的缩放比例和显示器物理像素宽度来计算。我将div宽度也设为了1280px(css像素),当缩放为100%的时候,DIV恰好撑满整个屏幕,不会出现横向滚动条。(这说明缩放比例为100%的时候一个CSS像素完全等于一个设备独立像素。)

这里有一个小坑点。如果纵向滚动条存在的话,它会占据一点点宽度,这时如果我还将元素宽度设为屏幕宽度1280px,屏幕就无法装下整个元素,然后就会出现横向滚动条。
在上面这个例子中因为纵向滚动条不存在,所以没有这个问题,将来在开发的时候要注意。

注意到我设置了5px的橙色边框,为什么最终的宽度不是1280+5+5=1290呢?因为我给所有元素加上了box-sizing: border-box的样式,这样我设置的边框宽度就会包含在width中,也就是最终加上边框以后宽度为1280px

【响应式布局】理解设备像素、设备独立像素和css像素
当我缩小浏览器窗口的时候滚动条出现了。因为div的宽度没有改变,无论以什么单位衡量(设备像素、设备独立像素还是CSS像素)。

因为缩放依然是100%,css像素边长/设备独立像素边长依然是1:1。

【响应式布局】理解设备像素、设备独立像素和css像素
将窗口最大化,并通过Ctrl+鼠标滚轮将浏览器缩放调整为200%,屏幕只能显示DIV的左半部分了,这时DIV的宽度依然是1280个css像素,但是它宽度变成了2560个设备独立像素。


以下是测试用的简单代码,大家可以自己在Chrome中试试!

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
  <title>test</title>
  <style>
    * {
      padding: 0;
      margin: 0;
      box-sizing: border-box;
    }

    body {
      text-align: center;
    }

    #box {
      /* 这里的width设置为screen.width的值,screen.width以设备独立像素为单位,给出屏幕的宽度。
      从而,在缩放为100%时,#box的宽度恰好等于屏幕宽度。 */
      width: 1280px;
      /* 限制高度防止出现纵向滚动条 */
      max-height: 100vh;
      background: lightblue;
      border: 5px solid orangered;
      border-radius: 20px;
    }

    /*
      media query rule中px的单位是设备独立像素,与缩放比例无关!
      也就是说,仅仅通过缩放窗口,不可能触发以下样式的切换。
    */
    @media (max-width: 640px) {
      #box {
        background-color: aqua;
      }
    }
  </style>
</head>

<body>
  <div id="box">this is box<br> 1
    <br> 2
    <br> 3
    <br> 4
    <br> 5
    <br> 6
    <br> 7
    <br> 8
    <br> 9
    <br> 10
    <br>
  </div>
</body>

</html>

参考资料

  1. A tale of two viewports — part one
  2. A pixel is not a pixel - MDN

欢迎阅读下一篇文章:initial containing block、viewport以及相关尺寸,它使用这篇文章介绍的3个概念,来解释响应式布局中有关的概念和属性。

相关推荐