仓库源文站点原文


title: 知识点整理 - 前端涉及的各种像素概念以及 viewport 汇总 layout: post thread: 178 date: 2017-07-09 author: Joe Jiang categories: Document tags: [CSS, Pixel, Viewport]

excerpt: 什么是物理像素、虚拟像素、逻辑像素、设备像素,什么又是 PPI, DPI, DPR 和 DIP?有关 viewport 以及苹果安卓设备上的页面呈现为什么效果不一样,又有哪些方法去改变和统一呢?网络上有很多资源对这些知识点进行了介绍,但是查看之后我发现大都比较零散且阅读顺序容易让新人疑惑,在这里我尝试根据几篇文章糅合了一个循序渐进的知识点整理...

最近在做车站地图页面的时候发现同为 0.5px 的设置,在不同分辨率的设备上显示效果有很大差异,有些安卓设备上渲染像素则为 0 了。这背后涉及到的知识点包括设备上不同像素概念的含义,同时也包括不同设备对渲染的处理机制等问题。之前为了赶上项目上线没有时间总结,现在有时间向背后进一步探寻了。发现存在很多困惑,什么是物理像素、虚拟像素、逻辑像素、设备像素,什么又是 PPI, DPI, DPR 和 DIP?有关 viewport 以及苹果安卓设备上的页面呈现为什么效果不一样,又有哪些方法去改变和统一呢?

网络上有很多资源对这些知识点进行了介绍,但是查看之后我发现大都比较零散且阅读顺序容易让新人疑惑,在这里我尝试根据根据自己的理解,将几篇介绍比较详细的文章糅合成一个循序渐进的知识点整理。在正式开始介绍之前,我们先集中看看两个基本概念。

关于像素的那些事

前面说到的 px 相对单位指的是图像显示的基本单元,它既不是一个确定的物理量,也不是一个点或者小方块,而是一个抽象概念。 刚刚提到了图像显示的基本单元,这个东西在不同设备上又是不一样的,例如显示器上的物理像素指的是显示器的点距,而打印机的物理像素则指的是打印机的墨点。

作为一个抽象概念,CSS 像素又具有两个方面的相对性,即:

所以,CSS中的1px(CSS像素 可变)!== 设备的1px(设备像素 不可变)

CSS 像素的真正含义

按照 CSS 规范的定义,CSS 中的 px 是一个相对长度,它相对的,是 viewing device 的分辨率。这个 viewing device,通常就是电脑显示器。典型的电脑显示器的分辨率是96DPI,也就是1像素为1/96英寸(实际上,假设我们的显示器分辨率都与物理分辨率一致,而液晶点距其实是0.25mm到0.29mm之间,所以不太可能是正好1/96英寸,而只是接近)。

一般来说,px 就是对应设备的物理像素,然而如果输出设备的解析度与电脑显示器大不相同,输出效果就会有问题。例如打印机输出到纸张上,其解析度比电脑屏幕要高许多,如果不缩放,直接使用设备的物理像素,那电脑上的照片由 600DPI 的打印机打出来就比用显示器看小了约6倍。

由于不同的物理设备的物理像素的大小是不一样的,所以 CSS 认为浏览器应该对 CSS 中的像素进行调节,使得浏览器中 1个 CSS 像素的大小在不同物理设备上看上去大小总是差不多,目的是为了保证阅读体验的一致。为了达到这一点,浏览器可以直接按照设备的物理像素大小进行换算,而 CSS 规范中使用参考像素来进行换算。

一个参考像素即为从一臂之遥看解析度为 96DPI 的设备输出(即1英寸96点)时,1点(即1/96英寸)的视角。它并不是1/96英寸长度,而是从一臂之遥的距离处看解析度为 96DPI 的设备输出一单位(即1/96英寸)时视线与水平线的夹角。通常认为常人臂长为28英寸,所以它的视角是: (1/96)in / (28in 2 PI / 360deg) = 0.0213度。如下图是一个示意图:

对于人来说,眼睛看到的大小取决于可视角度。而可视角度取决于物体的实际大小以及物体与眼睛的距离。10米远处一个1米见方的东西,与1米远处的10厘米见方的东西,看上去的大小差不多是一样的,所谓一叶障目不见泰山,讲的就是这个常识。

左边的屏幕(可以认为是电脑屏幕)的典型视觉距离是71厘米即28英寸,其1px对应了0.28mm;而右边的屏幕(可以认为是你的42寸高清电视)的典型视觉距离是3.5米即120英寸,其1px对应1.3mm。42寸的1080p电视,分辨率是1920*1080,则其物理像素只有0.5mm左右。

综上,px 是一个相对单位,而且在特定设备上总是一个近似值(原则是尽量接近参考像素)。

影响像素的那些因素

然而,如果你把绝对单位理解为对输出效果的绝对掌控,事情却大相径庭。就网页输出的最主要对象——电脑屏幕来说,px 可被视为一个基准单位——与桌面分辨率一致,如果是液晶屏,则几乎总是与液晶屏物理分辨率一致——也就是说网页设计者设定的1px,就是“最终打开这个网页的用户在显示器上看到的1个点距”!反倒是那些绝对单位,其实一点也不绝对。

那么 px 都会受哪些因素的影响而变化

当用于描述显示器时,我们可以吧 ppi 和 dpi 认为是同一个概念。那么 ppi 和 dpr 到底是什么关系呢? ppi 用作显示设备的工业标准,业界人士用 ppi 的值来衡量一个屏幕是否为高清屏,然后根据得到的密度分界来获得此时对应的 dpr 值,也即默认缩放比例。 dpr 和 ppi 相关,一般 dpr 为 ppi/160 的整数倍,如下所示:

项名 ldpi mdpi hdpi xhdpi
密度分界(密度值) 120 160 240 320
屏幕尺寸(分辨率) 240×320 320×480 480×800 640×960
默认缩放比例 0.75 1.0 1.5 2.0

了解了这两个概念后,我们可以来说说导致 CSS 中 px 变化的因素了。

如何理解上面说到的缩放呢?放大1倍,原来 1px 的东西变成 2px,但 1px 变为 2px 并不是把原来的 320px 变为 640px,而是在实际宽度不变的情况下,1px 变得跟原来的 2px 的长度(长宽)一样了(元素会占据更多的设备像素),所以放大1倍后原来需要 320px 才能填满的宽度现在只需要 160px,也即原来 320px 的面积里现在只能填入 160px 的东西了。

举个例子说明 CSS 像素的相对性,如下示意图:

作为Web开发者,我们接触的更多的是用于控制元素样式的样式单位像素。这里的像素我们称之为CSS像素。假设我们用PC浏览器打开一个页面,浏览器此时的效果如左图所示,但如果我们把页面放大(通过“Ctrl键”加上“+号键”),此时块状容器则横向扩张,如右图所示(黑色为实际效果,灰色为原来效果)。吊诡的是此时我们既没有调整浏览器窗口大小,也没有改变块状元素的css宽度,但是它看上去却变大了一倍——这是因为我们把CSS像素放大为了原来的两倍。

也就是说默认情况下一个CSS像素应该是等于一个物理像素的宽度的,但是浏览器的放大操作让一个CSS像素等于了多个设备像素宽度。

像素换算与倍率

DPR = 设备像素 / CSS像素 = 屏幕横向设备像素 / 理想视口的宽
CSS像素 = 设备独立像素 = 逻辑像素

有关倍率,我们用 iPhone 3gs 和 4s 来举例。假设有个邮件列表界面,我们不妨按照PC端网页设计的思维来想象。3gs上大概只能显示4-5行,4s就能显示9-10行,而且每行会变得特别宽。但两款手机其实是一样大的。如果照这种方式显示,3gs上刚刚好的效果,在4s上就会小到根本看不清字。

在现实中,这两者效果却是一样的。这是因为Retina屏幕把2x2个像素当1个像素使用。比如原本44像素高的顶部导航栏,在Retina屏上用了88个像素的高度来显示。导致界面元素都变成2倍大小,反而和3gs效果一样了。画质却更清晰。

在以前,iOS应用的资源图片中,同一张图通常有两个尺寸。你会看到文件名有的带@2x字样,有的不带。其中不带@2x的用在普通屏上,带@2x的用在Retina屏上。只要图片准备好,iOS会自己判断用哪张,Android道理也一样。

由此可以看出,苹果以普通屏为基准,给Retina屏定义了一个2倍的倍率(iPhone 6plus除外,它达到了3倍)。实际像素除以倍率,就得到逻辑像素尺寸。只要两个屏幕逻辑像素相同,它们的显示效果就是相同的。

Viewport

都有哪些 viewport?

什么是 viewport? 通俗的讲,移动设备上的viewport就是设备的屏幕上能用来显示我们的网页的那一块区域,在具体一点,就是浏览器上(也可能是一个app中的webview)用来显示网页的那部分区域,但viewport又不局限于浏览器可视区域的大小,它可能比浏览器的可视区域要大,也可能比浏览器的可视区域要小。在默认情况下,一般来讲,移动设备上的viewport都是要大于浏览器可视区域的,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能在移动设备上正常显示那些传统的为桌面浏览器设计的网站,移动设备上的浏览器都会把自己默认的viewport设为980px或1024px(也可能是其它值,这个是由设备自己决定的),但带来的后果就是浏览器会出现横向滚动条,因为浏览器可视区域的宽度是比这个默认的viewport的宽度要小的。

首先,移动设备上的浏览器认为自己必须能让所有的网站都正常显示,即使是那些不是为移动设备设计的网站。但如果以浏览器的可视区域作为viewport的话,因为移动设备的屏幕都不是很宽,所以那些为桌面浏览器设计的网站放到移动设备上显示时,必然会因为移动设备的viewport太窄,而挤作一团,甚至布局什么的都会乱掉。也许有人会问,现在不是有很多手机分辨率都非常大吗,比如768x1024,或者1080x1920这样,那这样的手机用来显示为桌面浏览器设计的网站是没问题的吧?前面我们已经说了,css中的1px并不是代表屏幕上的1px,你分辨率越大,css中1px代表的物理像素就会越多,DPR 的值也越大,这很好理解,因为你分辨率增大了,但屏幕尺寸并没有变大多少,必须让css中的1px代表更多的物理像素,才能让1px的东西在屏幕上的大小与那些低分辨率的设备差不多,不然就会因为太小而看不清。所以在1080x1920这样的设备上,在默认情况下,也许你只要把一个div的宽度设为300多px(视 DPR 的值而定),就是满屏的宽度了。为了防止某些网站因为viewport太窄而显示错乱,所以这些浏览器就决定默认情况下把viewport设为一个较宽的值,比如980px,这样的话即使是那些为桌面设计的网站也能在移动浏览器上正常显示了。ppk大神 把这个浏览器默认的viewport叫做 layout viewport。这个 layout viewport 的宽度可以通过 document.documentElement.clientWidth 来获取。

然而,layout viewport 的宽度是大于浏览器可视区域的宽度的,所以我们还需要一个viewport来代表 浏览器可视区域的大小,我们叫他 visual viewport。visual viewport 的宽度可以通过 window.innerWidth 来获取,但在 Android 2, Oprea mini 和 UC 8 中无法正确获取。下图为两个 viewport 的示意图:

现在我们已经有两个viewport了 - layout viewport 和 visual viewport。但浏览器觉得还不够,因为现在越来越多的网站都会为移动设备进行单独的设计,所以必须还要有一个能完美适配移动设备的 viewport。所谓的完美适配指的是,首先不需要用户缩放和横向滚动条就能正常的查看网站的所有内容;第二,显示的文字的大小是合适,比如一段14px大小的文字,不会因为在一个高密度像素的屏幕里显示得太小而无法看清,理想的情况是这段14px的文字无论是在何种密度屏幕,何种分辨率下,显示出来的大小都是差不多的。当然,不只是文字,其他元素像图片什么的也是这个道理,这就是第三个 viewport ——移动设备的理想 viewport (ideal viewport)。

ideal viewport 并没有一个固定的尺寸,不同的设备拥有有不同的 ideal viewport。所有 iPhone 的 ideal viewport 宽度都是320px,无论它的屏幕宽度是320还是640,也就是说,在 iPhone 中,css 中的 320px 就代表 iPhone 屏幕的宽度。

但是安卓设备就比较复杂了,有320px的,有360px的,有384px的等等,关于不同的设备ideal viewport的宽度都为多少,可以到 http://viewportsizes.com 去查看一下,里面收集了众多设备的理想宽度。

总结一下,ppk把移动设备上的viewport分为layout viewport, visual viewport 和 ideal viewport 三类:

利用 meta 标签对 viewport 进行控制

viewport是专为手机浏览器设计的一个meta标签,一个简单的示例如下所示:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

其中,width=device-width 表示此宽度不依赖于原始象素(px),而依赖于屏幕的宽度。移动设备默认的viewport是layout viewport,也就是那个比屏幕要宽的viewport,但在进行移动设备网站的开发时,我们需要的是ideal viewport。我们在开发移动设备的网站时,最常见的的一个动作就是把上面这个东西复制到我们的head标签中,它的作用是让当前的 viewport 宽度等于设备宽度,同事不允许用户手动缩放。也许允不允许用户缩放不同的网站有不同的要求,但让viewport的宽度等于设备的宽度,这个应该是大家都想要的效果,如果你不这样的设定的话,那就会使用那个比屏幕宽的默认viewport,也就是说会出现横向滚动条。

这个name为viewport的meta标签到底有哪些东西呢,又都有什么作用呢?meta viewport 标签首先是由苹果公司在其safari浏览器中引入的,目的就是解决移动设备的viewport问题。后来安卓以及各大浏览器厂商也都纷纷效仿,引入对meta viewport的支持,事实也证明这个东西还是非常有用的。在苹果的规范中,meta viewport 有6个可以设置的内容:

内容 含义
width 设置layout viewport 的宽度,为一个正整数,或字符串"device-width"
initial-scale 设置页面的初始缩放值,为一个数字,可以带小数
minimum-scale 允许用户的最小缩放值,为一个数字,可以带小数
maximum-scale 允许用户的最大缩放值,为一个数字,可以带小数
height 设置layout viewport 的高度,这个属性对我们并不重要,很少使用
user-scalable 是否允许用户进行缩放,值为"no"或"yes", no 代表不允许,yes代表允许

这些属性可以同时使用,也可以单独使用或混合使用,多个属性同时使用时用逗号隔开就行了。此外,在安卓中还支持 target-densitydpi 这个私有属性,它表示目标设备的密度等级,作用是决定css中的1px代表多少物理像素,但作为将要废弃的属性,所以使用中需要避免该用法。我们接下来看看具体的几个用法:

要想清楚这件事情,首先你得弄明白这个缩放是相对于什么来缩放的,因为这里的缩放值是1,也就是没缩放,但却达到了 ideal viewport 的效果,所以,那答案就只有一个了,缩放是相对于 ideal viewport来进行缩放的。

但如果width 和 initial-scale=1同时出现,并且还出现了冲突呢?

<meta name="viewport" content="width=400, initial-scale=1">

width=400表示把当前viewport的宽度设为400px,initial-scale=1则表示把当前viewport的宽度设为ideal viewport的宽度,那么浏览器到底该服从哪个命令呢?是书写顺序在后面的那个吗?不是。当遇到这种情况时,浏览器会取它们两个中较大的那个值。例如,当width=400,ideal viewport的宽度为320时,取的是400;当width=400, ideal viewport的宽度为480时,取的是ideal viewport的宽度。(ps:在uc9浏览器中,当initial-scale=1时,无论width属性的值为多少,此时viewport的宽度永远都是ideal viewport的宽度)

最后,总结一下,要把当前的viewport宽度设为ideal viewport的宽度,既可以设置 width=device-width,也可以设置 initial-scale=1,但这两者各有一个小缺陷,就是iphone、ipad以及IE 会横竖屏不分,通通以竖屏的ideal viewport宽度为准。所以,最完美的写法应该是,两者都写上去,这样就 initial-scale=1 解决了 iphone、ipad的毛病,width=device-width则解决了IE的毛病。关于缩放,我们可以得出以下一个式子:

当前缩放值 = ideal viewport宽度  / visual viewport宽度

大多数浏览器都符合这个理论,但是安卓上的原生浏览器以及IE有些问题。安卓自带的webkit浏览器只有在 initial-scale = 1 以及没有设置width属性时才是表现正常的,也就相当于这理论在它身上基本没用;而IE则根本不甩initial-scale这个属性,无论你给他设置什么,initial-scale表现出来的效果永远是1。

好了,现在再来说下initial-scale的默认值问题,就是不写这个属性的时候,它的默认值会是多少呢?很显然不会是1,因为当 initial-scale = 1 时,当前的layout viewport宽度会被设为 ideal viewport的宽度,但前面说了,各浏览器默认的 layout viewport宽度一般都是980啊,1024啊,800啊等等这些个值,没有一开始就是 ideal viewport的宽度的,所以 initial-scale的默认值肯定不是1。安卓设备上的initial-scale默认值好像没有方法能够得到,或者就是干脆它就没有默认值,一定要你显示的写出来这个东西才会起作用,我们不管它了,这里我们重点说一下iphone和ipad上的initial-scale默认值。

根据测试,我们可以在iphone和ipad上得到一个结论,就是无论你给layout viewpor设置的宽度是多少,而又没有指定初始的缩放值的话,那么iphone和ipad会自动计算initial-scale这个值,以保证当前layout viewport的宽度在缩放后就是浏览器可视区域的宽度,也就是说不会出现横向滚动条。比如说,在iphone上,我们不设置任何的viewport meta标签,此时layout viewport的宽度为980px,但我们可以看到浏览器并没有出现横向滚动条,浏览器默认的把页面缩小了,此时值应为 0.33 左右。

所以总结一下:在iphone和ipad上,无论你给viewport设的宽的是多少,如果没有指定默认的缩放值,则iphone和ipad会自动计算这个缩放值,以达到当前页面不会出现横向滚动条(或者说viewport的宽度就是屏幕的宽度)的目的。

JavaScript 动态更改 meta viewport 标签

document.write('<meta name="viewport" content="width=device-width,initial-scale=1">');
<meta id="testViewport" name="viewport" content="width = 380">
<script>
let mvp = document.getElementById('testViewport');
mvp.setAttribute('content','width=480');
</script>

但是要注意,在安卓2.3(或许是所有2.x版本中)的自带浏览器中,对meta viewport标签进行覆盖或更改,会出现让人非常迷糊的结果。

总结一下,每个移动设备浏览器中都有一个理想的宽度,这个理想的宽度是指css中的宽度,跟设备的物理宽度没有关系,在css中,这个宽度就相当于100%的所代表的那个宽度。我们可以用meta标签把viewport的宽度设为那个理想的宽度,如果不知道这个设备的理想宽度是多少,那么用device-width这个特殊值就行了,同时initial-scale=1也有把viewport的宽度设为理想宽度的作用。

为什么需要有理想的viewport呢?比如一个分辨率为320x480的手机理想viewport的宽度是320px,而另一个屏幕尺寸相同但分辨率为640x960的手机的理想viewport宽度也是为320px,那为什么分辨率大的这个手机的理想宽度要跟分辨率小的那个手机的理想宽度一样呢?这是因为,只有这样才能保证同样的网站在不同分辨率的设备上看起来都是一样或差不多的。

当然,以上内容对于了解像素概念及 viewport 原理有很大的帮助,但是实际开发中为了提高效率还是需要我们去复用已经实现的代码 snippet, 千万不要像我刚开始还总是想着“重复制造轮子”,幸好在师兄的指导下改正了过来。感激。

参考整理自如下来源