像素的相关知识浅析

像素、分辨率和屏幕像素密度

首先来了解一下这些名词的相关定义:

  • 在由一个数字序列表示的图像中的一个最小单位,称为像素,是一个单一颜色的小格存在。
    像素的思想派生出几个其它类型的概念,如体素纹素等,是组成三维纹理最小单位

  • 屏幕分辨率水平像素数目 * 垂直像素数目的表示。

  • 屏幕像素密度是显示设备包含的像素密度,一般称为PPI(Pixels Per Inch, 常用于图像)。
    与之相近的一个概念是DPI(Dots Per Inch,常用于印刷行业),且DPI表示水平方向或垂直方向,PPI表示对角线方向。
    公式:PPI = 根号下(水平像素的平方 + 垂直像素的平方) / 对角线的英寸长度

根据这些定义,我们来深入思考一些问题:

  • 像素尺寸是多大?
    由定义我们可知,像素即是一个单一颜色的小格,所以像素的大小并没有统一标准,厂商可以自行设置。
    当然,对于同一块屏幕尺寸,像素越多,屏幕分辨率越高,屏幕像素密度越密集,可以表现的细节越丰富,显示越精细。

  • 屏幕分辨率的调整原理?
    屏幕本来是一个物理设备,从厂商制作出厂后,其像素大小屏幕分辨率理应是固定不变的。
    但我们在电脑设置中往往可以看到一个调整分辨率的功能,这其实是因为我们操作的是一个操作系统,它充当着一个中间层的角色:

    • 向下通过操作系统控制显卡输出的内容尺寸,从而控制显示器的显示尺寸。物理的屏幕分辨率是不变的,显示的分辨率是动态调整的。
    • 向上暴露相关参数给用户,使其方便地动态调整屏幕分辨率

    所以我们在下面提到的物理像素实际上也并不是真正的物理像素点,也是经过操作系统封装后的逻辑像素

CSS像素和DPR

在前端CSS代码中,我们也常常用像素来控制页面UI的尺寸大小(单位px)。这里的像素又和上面提及的物理像素不同:

如上所述,物理像素的大小是任意的,并没有一个统一标准。如果CSS直接采用物理像素,则页面在不同设备之间的显示尺寸可能存在差异。

为了使CSS像素能够在不同的设备上显示尺寸一致,规范对于CSS像素有一些规定:

  • 对于高分辨率设备(打印机等),1px就等于96分之一英寸,约等于0.2646mm
  • 对于低分辨率设备(绝大部分显示器,手机屏幕),建议CSS像素参考最接近参考像素整数个物理像素

这里我们先来了解下参考像素(reference pixel)的概念:

参考像素

根据规范,一个参考像素即为从一臂之遥看解析度为96DPI的设备输出(即1英寸96点)时,1点(即1/96英寸)的视角(visual angle)。

通常认为常人臂长为28英寸,所以参考像素为: (1 / 96)in / (28in * 2 * PI / 360deg) = 0.0213度

参考像素的原理

这个规范的原理是什么呢?

近小远大:近处的物体显示的小一点,远处的物体显示的大一点,从而使两者看起来大致保持一致,而这一特点通过视角来实现。

从上图也可以看出,参考像素对应的这个视角,映射到具体屏幕上:

  • 对于28英寸远的屏幕来说,一个参考像素大致为0.26mm
  • 对于140英寸远的屏幕来说,一个参考像素大致为1.3mm

这个距离又是什么呢?

一般是由设备的典型视距决定的,在出厂时已经确定。比如视距:电视 > 电脑 > 手机。

CSS像素低分辨率设备就基于这个参考像素,从而实现1个CSS像素不同视距的屏幕上,显示的尺寸大致相同。

而且最接近参考像素的整数个的物理像素,也被称为DPR(= 物理像素分辨率 / CSS像素分辨率)。一般在js中可以通过window.devicePixelRatio获取。

DPR的变化

这里又有一个问题:我调整屏幕分辨率会不会影响到DPR的大小?

推导如下:

  • 屏幕分辨率调低会使得物理像素分辨率降低
  • 像素点变大,因为这里的像素点是被操作系统封装的逻辑像素(1个逻辑像素对应的多个真实物理像素变多)
  • 参考像素是一定的,所以1个CSS像素对应的整数个物理像素会减少,DPR也会降低

但实际上经过测试:无论屏幕分辨率怎么变更,DPR都保持不变。

具体原因未知,不过规范上有这样一句话,应该指的就是这个:

Note that this definition of the pixel unit and the physical units differs from previous versions of CSS. In particular, in previous versions of CSS the pixel unit and the physical units were not related by a fixed ratio: the physical units were always tied to their physical measurements while the pixel unit would vary to most closely match the reference pixel. (This change was made because too much existing content relies on the assumption of 96dpi, and breaking that assumption breaks the content.)

注意,这里像素单位物理单位的定义与CSS之前版本不同。特别地,在CSS之前版本中,像素单位物理单位固定比例(fixed ratio)无关:物理单位总是与它们的物理测量紧密相关,而像素单位会变得最接近参考像素(出现这种变化是因为太多现有内容依赖96dpi的假设,而打破这个假设就会破坏这些内容)

与调整屏幕分辨率相似的一个概念是内容缩放,在windows10系统中同时提供了这两个功能。
内容缩放(系统缩放,对网页进行缩放也可)会同时影响DPR的大小。

其实这个比较好理解,内容缩放相当于直接缩放CSS像素,所以1个CSS像素对应的物理像素会相应变化。

这里还有个细节:缩放导致window.devicePixelRatio可能出现小数。

这说明window.devicePixelRatio并不是真正严格地表示1个CSS像素对应几个物理像素
它代表着内容缩放的一个平均比率,具体的对应关系由系统通过相应算法实现。

Canvas在视网膜屏下绘制图片的失真问题

在视网膜屏下,1个CSS像素对应多个物理像素。 对于一个2倍屏500 * 300屏幕想要绘制一个高清图片,图片分辨率至少要大一倍,因为屏幕实际对应的是1000 * 600物理像素

和多倍图类似,在Canvas绘制图片进行缩放处理,否则会导致失真问题:

// 对于一个300 * 150画布:
<canvas width="300" height="150"></canvas>

// 如果直接绘制:
ctx.drawImage(image, 0, 0, 300, 150);

// 浏览器会计算出Canvas绘制缓冲区大小
// (300 * backingStorePixelRatio, 150 * backingStorePixelRatio)

/* 
 * backingStorePixelRatio是Canvas的缓冲像素比
 * 曾经在safari上是2,即和window.devicePixelRatio保持一致,不需要额外处理自动高清
 * 而在chrome上是1,缓冲区大小(300, 150),但物理像素为(600, 300),所以图片会放大模糊,需要额外处理
 *
 * 目前backingStorePixelRatio这一属性已经被废止,所以我们要手动实现backingStorePixelRatio的效果
 */
ctx.drawImage(image, 0, 0, 300 * window.devicePixelRatio , 150 * window.devicePixelRatio);

/* 
 * 但是直接这样绘制,图片也会被放大显示;
 * 如果要保持图片原来大小,可以利用Canvas画布和dom大小不一致会缩放的特点,缩放内容
 */
myCanvas.style.width = myCanvas.width + 'px';
myCanvas.style.height = myCanvas.height + 'px';
myCanvas.width = myCanvas.width * window.devicePixelRatio;
myCanvas.height = myCanvas.height * window.devicePixelRatio;

ctx.drawImage(image, 0, 0, myCanvas.width , myCanvas.height);

/* 
 * 如果canvas只有图片,那这样没有什么问题;
 * 如果有其他元素,如文字、图形时,它们也会被缩小
 * 我们利用scale来调整
 */
myCanvas.style.width = myCanvas.width + 'px';
myCanvas.style.height = myCanvas.height + 'px';
myCanvas.width = myCanvas.width * window.devicePixelRatio;
myCanvas.height = myCanvas.height * window.devicePixelRatio;

ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
ctx.drawImage(image, 0, 0, parseInt(myCanvas.style.width, 10) , parseInt(myCanvas.style.height, 10))

viewport

相对于电脑来说,手机屏幕是很"窄的",而随着智能手机的发展,其屏幕分辨率也在不断提高。
上面我们已经了解了:

  • 随着手机屏幕分辨率的提高,屏幕面积基本不变,物理像素变小,PPI增加
  • 对于手机来说其视距一定,参考像素是一定的,CSS像素也是一定的
  • 变化的只是一个CSS像素对应几个物理像素屏幕分辨率的提高也只是降低颗粒感,提高清晰度

retina对比图

所以移动端全屏宽度对于CSS像素来说也就是300px左右。

接下来,我们来了解一下viewport

viewport其实就是浏览器页面内容显示的那一块区域,注意它和<HTML>的关系:

  • viewport的宽度通过document.documentElement.clientWidth来获取
  • <HTML>的宽度通过document.documentElement.offsetWidth来获取
  • 默认情况下,<HTML>继承自viewport,但也可以自定义<HTML>的宽高

layout viewport

对于PC端,viewport就是浏览器的内容区域;但对于移动端我们不能这样直接指定:

  • 移动端全屏宽度300px左右,如果<HTML>是默认的(如100%),即300px左右;
  • 用这个宽度来显示本该是1000px宽度的PC端页面,可能导致整个页面挤作一团。

为了能使移动设备上的浏览器正常显示PC端页面,我们引入了第一个默认viewportlayout viewport

默认情况,viewport即为layout viewport,一般在1000px左右,根据设备不同而有所差异。

这也是为什么我们用手机看PC端页面时,如果没有相应的适配处理,页面会出现很长的滚动条。

layout viewport

visual viewport

如果不想获取整个内容区域的宽度,而只是获取当前可视区域的宽度。

可以通过window.innerWidth来获取,这个当前可视区域也被称为visual viewport

visual viewport

ideal viewport

上面的两个viewport是针对PC端页面显示的情况,移动端页面显示应该怎么处理?

既然移动端的全屏宽度300px左右,那我们直接将viewport设为这个宽度。
这样一来<HTML>100%也就是设备的全屏宽度,这个viewport被成为ideal viewport

当然不同的移动设备的全屏宽度是不尽相同的,如320px360px等。但它们是很接近的。

为了充分利用这两点情况,对于移动端的页面设计:

  • 设置ideal viewport<meta name="viewport" content="width=device-width" />
    device-width即移动设备的全屏宽度
  • 而设计师只需要出一份320px左右的设计稿,就可以适配大部分移动设备。

参考