CSS-设备像素与逻辑像素

  前端适配是个麻烦事。PC端要适配不同浏览器,移动端又要适配各种屏幕。而这一切的根源,要从「设备像素」和「逻辑像素」这对概念说起。

基础概念

  设备像素(Device Pixel)是屏幕的物理像素,一块屏幕出厂时就固定的发光点数量。比如 iPhone 8 是 750 × 1334 像素,这是硬件决定的,改不了。

  逻辑像素(CSS Pixel / Logical Pixel)是我们写代码时用的像素。CSS 里的 width: 375px 指的就是逻辑像素。

  为什么会有两套像素系统?因为手机屏幕太小了,如果 1px CSS 对应 1 个物理像素,文字和按钮会小到看不清。所以苹果在 iPhone 4 上提出了 Retina 概念,用一个逻辑像素对应多个物理像素。

DPR(Device Pixel Ratio)

  DPR 就是设备像素与逻辑像素的比值:

1
DPR = 设备像素 / 逻辑像素

  常见的 DPR 值:

设备 逻辑分辨率 物理分辨率 DPR
iPhone SE / 6/7/8 375 × 667 750 × 1334 2
iPhone 12 Pro 390 × 844 1170 × 2532 3
MacBook Pro Retina 1440 × 900 2880 × 1800 2
普通 1080p 显示器 1920 × 1080 1920 × 1080 1

  DPR = 1 时,一个 CSS 像素就是一个物理像素;DPR = 2 时,一个 CSS 像素由 2×2 个物理像素渲染,所以画面更细腻。

由 DPR 引发的问题

1. 1px 边框变粗

  最经典的问题:写 border: 1px solid #ccc,在 DPR = 2 的屏幕上,物理上实际渲染了 2px 的宽度,看起来比设计稿粗。解决方案一般用 transform: scale() 来模拟 0.5px:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* DPR = 2 下 0.5px 等效 */
.hairline {
position: relative;
}
.hairline::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: #ccc;
transform: scaleY(0.5);
transform-origin: 0 0;
}

2. 图片模糊

  一张 100×100px 的图片,在 DPR = 2 的屏幕上需要 200×200px 的物理像素来渲染,但实际只有 100×100px 的数据,就只能被拉伸,导致模糊。解决方案是用 srcset 提供不同倍率的图:

1
2
3
<img src="photo-1x.jpg"
srcset="photo-2x.jpg 2x, photo-3x.jpg 3x"
alt="photo">

或者用 CSS image-set()

1
2
3
4
5
6
.avatar {
background-image: image-set(
url("avatar-1x.jpg") 1x,
url("avatar-2x.jpg") 2x
);
}

3. 媒体查询

  有时需要针对不同 DPR 做不同布局,可以用 device-pixel-ratio

1
2
3
4
5
6
7
8
/* DPR = 2 */
@media (-webkit-min-device-pixel-ratio: 2) {
.box { border: 0.5px solid #ccc; }
}
/* DPR = 3 */
@media (-webkit-min-device-pixel-ratio: 3) {
.box { border: 0.333px solid #ccc; }
}

视口(Viewport)

  移动端还有一个「视口」概念。在没做适配的页面中,手机浏览器会用一个「虚拟视口」来容纳整个 PC 页面,导致文字看起来很小。解决方案是加 viewport meta 标签:

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

  width=device-width 让页面宽度等于设备宽度(逻辑像素),initial-scale=1.0 禁止默认缩放。这样 CSS 中的 100vw 就等于逻辑宽度,页面就正常了。

总结

  • 设备像素是屏幕物理发光点,写代码时一般不用管它
  • 逻辑像素是我们写 CSS 用的单位,通过 DPR 与物理像素关联
  • DPR > 1 导致 1px 边框变粗和图片模糊,需要用 transform + srcset 来适配
  • 移动端一定要加 viewport meta 标签