像素与分辨率
界面世界的原子是像素。无论多精致的设计、多复杂的动画,最终都归结为一件事:给屏幕上每个小方格指定一个颜色。而"1 个像素到底多大"这个看似幼稚的问题,答案居然有三种——搞不清这三种,你就永远不明白为什么设计稿要给 @2x、@3x 三份图。
凑到很近看老式路边的 LED 广告牌,你会发现它其实是一颗一颗小灯泡,每颗灯泡只能亮一种颜色。走远了,眼睛把它们混在一起,就成了一幅完整的画。
体育馆里几万人举牌做出的"人浪图案"也是这个道理——每个人只负责举一块牌子(一个像素),图案是从远处看才出现的。
屏幕就是这样一块巨大的、极其精细的"举牌方阵"。你手机上大约有三百多万个"人"在同时举牌,而且每秒换 60 次姿势。
像素是什么:屏幕上最小的发光单元
把手机屏幕放到显微镜下看,你会看到一件出乎意料的事:一个像素其实不是一个点,而是三个挨在一起的小灯——一个红、一个绿、一个蓝。它们叫子像素(Subpixel)。
"子像素"这个词听着玄,换成大白话就是:你以为的一个小方格,其实是一个塞了三支彩笔的小格子。这三支笔一支红、一支绿、一支蓝,谁用力谁不用力,格子最后显示出来的颜色就跟着变。这就好比绣花的时候,你手边只有三种颜色的线,但靠疏密搭配,一小块布上照样能绣出上百种色调。
为什么是红绿蓝这三个?因为人眼视网膜上正好有三种感知颜色的锥状细胞,分别对红、绿、蓝三段波长最敏感。所以只要用这三种光按不同比例混合,就能骗过人眼,让它"以为"看到了任何颜色。这套机制叫RGB 加色混合。
这里说的"骗过人眼"不是修辞。屏幕从来没有真的做出黄光,它只是同时点亮了红灯和绿灯,让你视网膜上的三种细胞收到了和"看见黄色"完全一样的刺激组合。打个比方:这就像做菜时你手里没有现成的高汤包,但把盐、糖、酱油按某个比例兑一勺,尝起来跟高汤几乎一样——舌头收到的信号对了,大脑就认了。
每个子像素的亮度通常用 0 到 255 共 256 级来表示。于是一个像素能表达的颜色数量是 256 × 256 × 256 ≈ 1678 万种——这就是"1600 万色 / 24 位真彩色"的来历。
1678 万这个数字大到没有感觉,不妨这样想:假如让你亲手把这 1678 万种颜色一个一个涂在色卡上,每张色卡一秒钟,不吃不喝不睡地涂,需要 194 天。而这只是一个像素能表达的颜色总数——你手机屏幕上有三百多万个这样的格子,每一个都能随时切换成这 1678 万种中的任意一种。
再补一句常被问到的:为什么偏偏是 256 级,不是 100 级或 1000 级?说白了是为了迁就计算机的存储习惯。计算机存数据以"字节"为单位,一个字节刚好能表示 0~255 这 256 个数,一个不多一个不少。用 256 级既不浪费,也刚好超过人眼在大多数场合能分辨的明暗档数。这就相当于超市卖鸡蛋按一板 30 个卖——不是 30 这个数字有魔力,而是这个规格刚好装得满、拿得走。
一个像素的内部构造与颜色计算
┌─────────────────┐
│ R │ G │ B │ ← 三个子像素,各 256 级亮度
└─────────────────┘
(255, 0, 0) → 纯红 :只有红灯全亮
(255, 255, 0) → 黄色 :红 + 绿全亮(不是红+黄!加色混合反直觉)
(255, 255, 255) → 白色 :三个全亮
( 0, 0, 0) → 黑色 :三个全灭
( 85,107, 61) → 本站主色 :#556b3d
可表达颜色数 = 256 × 256 × 256 = 16,777,216 种
这里有个值得注意的细节:纯黑在不同屏幕上不是一回事。液晶屏(LCD)的背光是常亮的,靠液晶层"挡光"来做黑,挡不干净,所以黑色其实是深灰。而 OLED 屏的每个子像素自己发光,要黑就直接断电不亮,那是真正的漆黑。这就是为什么 OLED 手机上的深色模式看起来特别"深",而且还更省电——黑色区域压根没通电。
这两种屏幕的差别,用生活场景一比就清楚了。LCD 相当于一间屋子的灯常开着,你想让某块地方变黑,只能拉一层窗帘去挡——窗帘再厚也会漏一点光,所以那块地方是"很暗的灰",不是真黑。OLED 相当于每块地方都有自己的独立开关,要黑就直接关灯,一点光都不漏,还顺带省了电费。所以在 OLED 手机上开深色模式,等于把屋子里大半的灯都关掉了,续航自然更长。
子像素的排列方式也不止一种。常见的 LCD 是横向 RGB 条状排列,而三星的 OLED 常用"钻石排列"(Pentile),红蓝子像素数量少于绿色。这会导致同样标称分辨率下,文字边缘的细节表现略有差异——早年 Pentile 屏被批评"文字发虚",就是这个原因。
为什么少了红蓝就会"发虚"?简单说,就是三支笔里有两支被砍成了半支,遇到需要细致描边的地方,笔不够用,只能糊过去。好比绣花时绿线充足、红线蓝线只有一半,绣大块的叶子没问题,绣红色的细花纹就得靠两针拼一针,凑近看边缘就毛了。人眼对绿色最敏感,所以厂商优先保绿——这是一次很精明的取舍,只是代价落在了文字边缘上。
还有一个很多人第一次听说会愣一下的技术,叫子像素渲染(Subpixel Rendering)。它说白了就是偷偷动用一个像素里的红绿蓝三个小灯,把它们当成三个独立的更小格子来用——好比一个格子里其实塞了三支不同颜色的笔,单独用其中一支,就能画出比一整格更细的线。代价是这根细线会带一点彩色的边(因为你用的确实是彩笔),但在正常观看距离上眼睛会把它混成灰色,反而觉得字更锐利。Windows 上的 ClearType 就是干这个的,这也是为什么把屏幕拍下来放大看,黑字边缘常常带着淡淡的橙色和蓝色毛边。
分辨率 vs 屏幕尺寸:两个完全不同的东西
这是新手最容易混的一对概念,也是买显示器时最容易踩的坑。
- 分辨率 Resolution屏幕横竖各有多少个像素,比如 1920 × 1080。它是数量,单位是"个",和屏幕多大完全无关。
- 屏幕尺寸 Screen Size对角线的物理长度,比如 27 英寸。它是长度,单位是英寸,和有多少像素完全无关。
- 像素密度 PPI前两者相除得到的第三个量:每英寸塞了多少个像素。它才真正决定"看起来清不清晰"。
为什么必须分清?举个极端例子:一块 6 英寸的手机屏和一块 60 英寸的电视,分辨率可以完全一样,都是 1920 × 1080。像素数量一模一样,但手机看起来细腻锐利,电视凑近看能数出格子。
打个比方,这就像同样一板 30 个鸡蛋,装在小蛋托里挤得满满当当,摊到一整张餐桌上就稀稀落落。鸡蛋数量没变,变的是"摊多大"。所以"分辨率高不高"和"看着清不清楚"根本就是两个问题,中间必须再插一个"屏幕多大"才连得上。
反过来也成立:一块 27 英寸的 1080p 显示器,和一块 24 英寸的 1080p 显示器,同样是 1080p,但 24 寸那块明显更清晰——因为同样多的像素被塞进了更小的面积里。
所以下次看到"高分辨率 = 高清"这种说法,要在心里补一句:决定清晰度的从来不是分辨率,而是分辨率与尺寸的比值。
顺手把"4K"这个营销词也翻译成人话。4K 指的是横向大约 4000 个像素(标准是 3840 × 2160),乘起来是 830 万个小格子。830 万有多少?假如你手动一秒填一个格子,不眠不休地填,要连着填 96 天;而你的显卡每秒要把这 830 万个格子全部算完并送屏 60 次。换句话说,你 96 天的手工量,它一秒钟干 60 遍。这也是为什么 4K 游戏对显卡这么苛刻——不是画面复杂了,是要填的格子多了四倍。
PPI / DPI:清晰度的真正指标
PPI(Pixels Per Inch,每英寸像素数)用于屏幕;DPI(Dots Per Inch,每英寸墨点数)本来是印刷术语,说的是打印机每英寸打多少个墨点。两者原理相通,日常口语里常混用,但严格来说:屏幕说 PPI,打印说 DPI。
这两个缩写换成大白话就一句:一英寸的长度里,挤了多少个格子。格子挤得越密,眼睛越数不出来,看着就越"高清"。它相当于十字绣里的"布的格数"——同样一块布,格子越细密,绣出来的图案越接近照片;格子粗,绣出来就是一格一格的马赛克感。
计算公式很简单——先用勾股定理算出对角线上有多少像素,再除以对角线的物理英寸数:
PPI = √(横向像素² + 纵向像素²) ÷ 屏幕对角线英寸
【例 1】iPhone 15:2556 × 1179,6.1 英寸
对角线像素 = √(2556² + 1179²) = √(6533136 + 1390041) ≈ 2814
PPI = 2814 ÷ 6.1 ≈ 461 → 非常细腻
【例 2】27 寸 1080p 显示器
对角线像素 = √(1920² + 1080²) ≈ 2203
PPI = 2203 ÷ 27 ≈ 82 → 凑近能看到颗粒
【例 3】27 寸 4K 显示器(3840 × 2160)
对角线像素 = √(3840² + 2160²) ≈ 4406
PPI = 4406 ÷ 27 ≈ 163 → 清晰,接近"视网膜"标准
那么多少 PPI 才算够?这里的关键是观看距离。人眼的角分辨力大约是 1 弧分,换算下来:手机通常在 25~30 厘米处使用,需要 300 PPI 以上才看不出颗粒;笔记本在 50 厘米左右,200 PPI 左右就够;电视在 3 米开外,50 PPI 都足够。
"角分辨力 1 弧分"这种说法太学术,翻译成人话是:你的眼睛能分开两个点的最小本事,大约相当于在 10 米外看清一枚一元硬币上的花纹粗细。再远、再小,两个点就在你视网膜上糊成一个了。所以"多少 PPI 才够"这个问题永远不能单独回答,必须先问"你离多远看"——就像判断一块路牌上的字够不够大,得先说清是站在路边看还是在马路对面看。
这也解释了一个反常识的现象:电视的 PPI 比手机低好几倍,但你看电视从来不觉得糊。因为你离电视三米远,眼睛的"采样能力"在那个距离上本来就下降了。相当于同一张地铁线路图,贴在车门上你能看清每一个小站名,挂在站台对面墙上你只能看清线路的走向——不是图变差了,是你退远了。
这解释了一个常见疑问:为什么 8K 电视看起来提升不大?因为在正常客厅的观看距离上,你的眼睛已经分辨不出 4K 和 8K 的差别了——不是屏幕不够好,是眼睛的采样率不够。
再算笔账你会更有体感:要在 3 米的沙发距离上真正看出 8K 相对 4K 的优势,电视对角线得做到 100 英寸以上,也就是差不多两米五宽的一面墙。绝大多数家庭客厅装不下这么大的屏,也没有那么远的观看距离。这就好比给一间 60 平的小区住房装一台工业级中央空调——参数确实更强,但屋子小得根本用不上那个功率。
把像素想成一袋糖粉。
分辨率 = 糖粉有多少粒;屏幕尺寸 = 要撒到多大面积上;PPI = 撒完以后每平方厘米有多少粒。
同样一袋糖,撒在小盘子上密密实实看不出颗粒(高 PPI);撒到操场上就稀稀拉拉,一眼能数出粒(低 PPI)。
所以"这块屏清晰吗"这个问题,光问"多少粒糖"是没有答案的——必须同时知道"撒多大"。
物理像素 vs 逻辑像素:一个 px 到底多大
现在到了本节最核心、也最容易让人困惑的地方。
假设你在 CSS 里写 width: 100px。在 2010 年之前,这句话的意思很朴素:占 100 个真实的屏幕像素。但当手机 PPI 冲到 400 以上后,这个逻辑彻底崩了——如果 100px 还是 100 个物理像素,那么一个手机上的按钮会小到只有几毫米,根本按不到。
这个崩塌过程用生活场景讲最清楚。想象一下你在装修时按"一块瓷砖"来量尺寸:说"这面墙宽 20 块砖"。只要工人用的砖是同一规格,这句话完全没问题。可有一天厂家把砖做小了一半,同样"20 块砖"的墙就只有原来一半宽了。屏幕像素就是那块砖——它悄悄变小了,而所有按"多少块砖"写的图纸全部作废。解决办法不是改图纸,而是换个不会变的单位:从"多少块砖"改成"多少厘米"。
于是业界引入了一层抽象,把"像素"这个词拆成了两个概念:
| 概念 | 别称 | 含义 | 谁在用 |
|---|---|---|---|
| 物理像素 | 设备像素 / Device Pixel | 屏幕上真实存在的那颗发光单元,硬件出厂就定死了 | 硬件、驱动、渲染最底层 |
| 逻辑像素 | CSS px / DIP / pt / dp | 一个抽象的长度单位,约定为"在标准观看距离下看起来差不多大",与硬件解耦 | 开发者写代码、设计师出稿 |
换句话说,CSS 里的 px 从来就不是"一个屏幕像素",它是一个视觉长度单位。W3C 的规范里把 1 个 CSS px 定义为在标准观看距离下约等于 1/96 英寸的视角大小。它更像"厘米",而不像"一颗灯泡"。
各平台给这个抽象单位起了不同名字,但说的是同一件事:Web 叫 CSS px,Android 叫 dp(density-independent pixel),iOS 叫 pt(point),Windows 叫 DIP。你写 44dp 或 44pt,在任何设备上摸起来都差不多大——系统会自动换算成对应数量的物理像素。
四个名字听着像四种技术,其实就是同一个东西的四个方言称呼,好比"馒头"在不同地方叫"馍""饽饽""炊饼"——东西是一个,只是各家叫法不同。你换平台时要改的只是单位后缀,脑子里的概念一点都不用动。
这层抽象带来的好处极大:你不再需要关心用户的屏幕有多密。你按"看起来该多大"来写代码,系统负责翻译。这跟你订做衣服时说"腰围 75 厘米"而不是"75 根纤维宽"是一个道理。
再换一个更日常的比方:逻辑像素相当于你在餐厅点菜时说"一份",物理像素相当于厨房里实际下了几克面。你只管说"一份",厨师负责按今天的食材规格换算成克数;哪天面粉换了牌子、一份该下多少克变了,也是厨房的事,跟你点菜的说法无关。开发者写逻辑像素,系统当厨师做换算——这就是这层抽象的全部意义。
把这一整节的五个概念,一次性放进一个十字绣的场景里,你会发现它们其实是一套很朴素的关系。
① 像素就是布上的一个小格子,一格只能扎一种颜色的线,这是最小单位,不能再切。
② 分辨率就是这块布横竖各有多少格——"200 格乘 300 格",说的是数量,跟布有多大没关系。
③ 屏幕尺寸就是这块布摊开来有多少厘米宽——说的是长度,跟有多少格没关系。
④ PPI 就是每厘米有几格。同样 200×300 格,绣在手帕上密不透风(高 PPI,看不出格子),绣在床单上一格一厘米(低 PPI,一眼数得清)。
⑤ 逻辑像素是图纸上标注的厘米数。图纸写"这朵花宽 3 厘米",绣在细布上要占 30 格,绣在粗布上只占 10 格——图纸永远不改,改的是"一厘米折算几格"这个换算率,而这个换算率就是 DPR。
把这套关系记住,本节所有让人头疼的问题都会自动解开:
· 为什么"1080p 就是高清"是错的?——因为只说了格数,没说布多大。
· 为什么 CSS 里的 px 不是一个屏幕像素?——因为 px 是图纸上的厘米,不是布上的格子。
· 为什么位图要出 @2x、@3x?——因为已经绣好的成品是按"多少格"定死的,换到更细的布上就得重新绣一幅格数更多的;而图纸(矢量)不用改,照着重绣一遍就行。
· 为什么 DPR 是小数时细线会发虚?——因为图纸要求"半格宽的线",可布上没有半格,只能用两格各扎一半的浅色线去凑,看着就毛了。
设备像素比 DPR:两套像素之间的汇率
既然有两套像素,就必须有一个换算比率。这个比率叫设备像素比(Device Pixel Ratio,DPR):
"设备像素比"四个字听着玄,说白了就是"汇率":你手里拿的是逻辑像素这种"记账货币",屏幕收的是物理像素这种"现金",DPR 就是今天的兑换比例。DPR=3 相当于 1 块记账币能换 3 块现金,你写 100 就得掏出 300 个真实的小灯来铺满它。
DPR = 物理像素数 ÷ 逻辑像素数
【普通显示器】DPR = 1
1 个逻辑像素 → 1 个物理像素 (一对一)
【iPhone / Retina 笔记本】DPR = 2
1 个逻辑像素 → 2×2 = 4 个物理像素 (一个格子里塞四颗灯)
【高端安卓旗舰】DPR = 3
1 个逻辑像素 → 3×3 = 9 个物理像素 (一个格子里塞九颗灯)
// 在浏览器里查看当前设备的 DPR:
console.log(window.devicePixelRatio); // 1、2、2.75、3 ...
// 逻辑分辨率(你写代码面对的) vs 物理分辨率(屏幕真实的)
console.log(window.innerWidth); // 例如 393(逻辑)
console.log(window.innerWidth * window.devicePixelRatio); // 例如 1179(物理)
这解释了一个让很多人困惑的现象:iPhone 15 的屏幕物理分辨率是 1179 × 2556,但你在 CSS 里拿到的宽度只有 393。因为 1179 ÷ 3 = 393——它的 DPR 是 3,三个物理像素排成一行才算一个逻辑像素。
注意 DPR 不一定是整数。很多安卓机是 2.625、2.75 这类小数,Windows 的缩放设置里选 125%、150% 也会得到 1.25、1.5。小数 DPR 是渲染问题的重灾区——1 逻辑像素的细线要画成 1.5 个物理像素,画不出半颗灯,只能靠灰度模糊来近似,于是细线看起来发虚。这就是老前端常抱怨的"1px 边框问题"。
为什么"画不出半颗灯"这件事这么要命?打个比方:这就像装修贴瓷砖,图纸要求一条 1.5 块砖宽的装饰带,可砖不能切一半——你要么贴 1 块(太细),要么贴 2 块(太粗),要么找一块半透明的砖凑在中间充当"半块",看起来就是一道模糊的过渡。浏览器选的是第三条路,用一排半灰的像素去假装"半个像素",于是那条本该刀切一样的细线,看着就像用铅笔轻轻描过一遍。
Retina 屏原理与 @2x / @3x 图
2010 年苹果推出 iPhone 4 时提出了 Retina(视网膜屏)这个营销词,它的技术含义就一句话:PPI 高到在正常使用距离上人眼分辨不出单个像素。
但苹果做的最聪明的一件事不是提高 PPI,而是同时把逻辑像素数保持不变。iPhone 3GS 是 320×480 物理像素,iPhone 4 变成 640×960 物理像素,但逻辑尺寸仍然是 320×480。结果是:所有老 App 不用改一行代码,按钮和文字还是原来那么大,只是每一个逻辑像素现在由四颗灯来显示,边缘变得极其平滑。
这一手的巧妙之处,用装修打个比方就懂了:相当于房东把所有房间的瓷砖换成了尺寸只有原来四分之一的小砖,但保证"这面墙还是 3 米宽"这个数字一点不改。住户拿着旧图纸照样能施工,家具还是摆得下;唯一的变化是砖缝细了、地面看着更平整。苹果改的是砖的规格,没改房子的尺寸——这是整个 Retina 策略的全部秘密。
这个策略的代价,落在了图片身上。
文字和矢量图形无所谓——它们是靠数学公式实时绘制的,DPR 多大就按多大精度画,天生清晰。但位图(PNG / JPG)里的像素是死的。一张 100×100 的图片,在 DPR=2 的屏幕上要占据 200×200 个物理像素,系统只能把每个原始像素拉大成四个——于是变糊。
"像素是死的"这句话,换成大白话就是:位图是已经绣好的成品,格数在出厂时就钉死了。你把一幅 100 格的绣品拿到 200 格的细布上去铺,每一格必须撑成四格,原来一针的地方现在要涂满一小片——那一小片里没有任何新信息,只能拿原来的颜色糊上去。所以放大后不是"变模糊",而是本来就没有那么多信息,只能拿旧信息硬撑场面。
解决办法就是准备多套图:
| 文件名 | 实际像素尺寸 | 适用 DPR | 显示出来的逻辑尺寸 |
|---|---|---|---|
icon.png | 100 × 100 | 1(普通屏) | 100 × 100 |
icon@2x.png | 200 × 200 | 2(Retina) | 100 × 100 |
icon@3x.png | 300 × 300 | 3(安卓旗舰) | 100 × 100 |
注意最后一列:三张图显示出来一样大,区别只在于高 DPR 屏幕上用的那张有更多真实像素可供利用,所以更清晰。这就是设计师交付时总要导出三份的原因——不是为了让图变大,而是为了喂饱那些密集的灯泡。
这一点特别容易被误解,所以再强调一遍:@3x 的图不是"更大的图",而是"同样大小、内部更细的图"。好比同一朵花的绣品,一幅绣在 10 格的粗布上,一幅绣在 30 格的细布上——摊开来两幅一样宽,但细布那幅的花瓣边缘是圆滑的,粗布那幅是阶梯状的。你要的从来不是更大的成品,而是格数更密的成品。
<!-- 让浏览器根据 DPR 自动挑选合适的图 -->
<img src="icon.png"
srcset="icon.png 1x, icon@2x.png 2x, icon@3x.png 3x"
width="100" height="100" alt="图标">
/* CSS 里的写法 */
.logo { background-image: url(logo.png); }
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.logo { background-image: url(logo@2x.png); background-size: 100px 100px; }
}
画布 canvas 也要做高清适配
上面讲的 @2x / @3x 思路,同样适用于 <canvas>。画布的默认物理像素数等于 CSS 尺寸,在 Retina 屏上画出来的东西是糊的,必须手动把画布的实际像素放大 DPR 倍,再把绘图坐标系缩放回来:
// Canvas 高清适配:三行标准操作
const dpr = window.devicePixelRatio || 1;
canvas.width = cssWidth * dpr; // 实际像素放大
canvas.height = cssHeight * dpr;
canvas.style.width = cssWidth + 'px'; // 显示尺寸保持不变
canvas.style.height = cssHeight + 'px';
ctx.scale(dpr, dpr); // 坐标系缩回来,后面照常用逻辑坐标画
这三行看着别扭,其实就是在干一件很日常的事:先按"更细的格子"重新买一块布,再把图纸上的厘米标注等比放大,最后照旧按厘米下针。如果只买了细布却不改图纸(漏掉 ctx.scale),画出来的东西会只占左上角四分之一;如果改了图纸却没买细布(漏掉前两行),画出来的东西还是糊的。两步必须配对,缺一步就出岔子。
四个最容易搞混的说法,逐个掰开
这一节的概念不多,但被日常用语搅得很浑。下面四句话你多半听过,每一句都错在一个具体的地方。
第一句 · "这块屏是 2K 的,所以比 1080p 清晰。"——错在把数量当成了密度。2K 只说明格子更多,没说这些格子摊在多大面积上。一块 32 英寸的 2K 显示器,PPI 大约 92;一块 24 英寸的 1080p,PPI 大约 92。两者的细腻程度几乎一样。相当于两个人都说"我买了 30 个鸡蛋",你还是不知道谁的蛋托更挤。
第二句 · "设计稿是 750px 宽,所以要按 750 写 CSS。"——错在混淆了两套单位。750 这个数字来自"iPhone 6 的物理宽度",而 CSS 里对应的逻辑宽度是 375。说白了,设计师给的是"布上有多少格",你要写的是"图纸上多少厘米",中间差一个 DPR 的除法。直接把 750 抄进代码,做出来的界面会大出一倍。
第三句 · "把图片导出得更大就能更清晰。"——错在方向。清晰度取决于"显示区域内有多少真实像素",而不是文件绝对尺寸。一张 3000px 宽的图放在 100px 宽的位置上显示,多出来的 2900 像素全部被丢弃,只白白吃掉流量和解码时间。该做的是按显示尺寸乘 DPR 出图,而不是一律出最大的。好比搬家时不管什么东西都往最大号纸箱里塞——箱子大了不代表东西装得更好,只是更难搬。
第四句 · "文字发虚是字体的问题。"——大部分情况下不是。文字发虚的头号原因是元素落在了非整数的物理像素位置上:比如某个容器因为 transform 或小数 DPR,被摆在了 X = 100.5 这个位置,于是整段文字的每一个笔画都要跨在两个格子之间,只能靠灰度去凑。这就像在瓷砖地面上贴一条装饰带,起点没对准砖缝,整条带子从头到尾都是错缝的。解决办法通常是让位移取整、或者避免对含文字的元素做非整数缩放。
四句话背后其实是同一个纪律:凡是涉及"清晰不清晰"的问题,先在心里把"格子数""摊多大""图纸单位""换算率"这四样分开摆好,再判断哪一环出了问题。这四样一混,怎么调都是碰运气。
开发中的六条实用建议
- 图标一律用 SVG矢量图不存在 DPR 问题,任何屏幕都清晰,体积还小、能改颜色。能用 SVG 就别导 PNG——这一条能省掉 90% 的适配麻烦。
- 位图按最高 DPR 准备照片和插画只能用位图。按 @3x 出图 + 用
srcset让浏览器自己选,别一股脑塞最大图,那会浪费低端设备的流量。 - 永远用逻辑像素写布局代码里只写 CSS px / dp / pt,绝不去手动乘 DPR。DPR 换算是系统的职责,你插手只会在某些设备上算错。
- 触控目标至少 44×44 逻辑像素苹果和 Google 的规范里,可点击区域的下限约是 44pt / 48dp。这是手指的物理尺寸决定的,和屏幕多密无关——高 PPI 屏上按钮不能因为"看着大"就做小。
- 警惕小数 DPR 上的 1px 线DPR=1.5 时 1px 细线会发虚。需要极细分割线时,用
transform: scaleY(0.5)或0.5px配合特性检测,而不是硬写 1px 然后祈祷。 - 测试要覆盖三档 DPR至少测 DPR=1(普通显示器)、2(Retina / 多数 iPhone)、3(安卓旗舰)。浏览器开发者工具的设备模拟可以直接切换 DPR,成本很低。
为什么这一节必须放在第 2 章开头
因为像素是本章后面所有内容的计量单位。
下一节讲位图和矢量的差别,本质就是"存像素值"还是"存绘制指令";讲颜色,就是给每个像素的 RGB 三个通道赋值;讲渲染管线,就是"如何高效地算出每个像素该是什么颜色";讲 60fps,就是"这几百万个像素必须在 16.7 毫秒内全部算完并送屏"。
换成大白话,后面四节其实在回答同一句话的四个部分:"谁来决定每个小格子填什么颜色,以及必须在多快之内填完。"§ 2.2 问的是"这张图是已经填好的成品,还是一份填色说明书";§ 2.3 问的是"一个格子里的颜色到底怎么记";§ 2.4 问的是"从一份网页文本到几百万个格子的颜色,中间要过几道工序";§ 2.5 问的是"这几道工序必须在多少时间内跑完"。四节合起来就是一条从"填什么"到"多快填完"的完整链条,而像素是这条链上唯一的计量单位。
把像素、分辨率、PPI、逻辑像素、DPR 这五个概念的关系理清了,后面四节都会顺理成章。而没理清的话,你会一直被"为什么我的图在这台机器上是糊的"这类问题反复困扰,却始终找不到病根。
最后留一句方法论:遇到任何显示相关的怪问题,先问"这个数字说的是哪一套像素"。十次里有七八次,答案就在这一句里。这就好比查水管漏水,先分清是自来水管还是排水管——问错了管子,工具再全也修不好。
像素是屏幕上能被独立控制颜色的最小单元,内部由 RGB 三个子像素组成,各 256 级,合计约 1678 万色。
分辨率(像素个数)与屏幕尺寸(物理英寸)是两个独立的量,两者相除得到的 PPI 才决定清晰度;公式是 √(横²+纵²) ÷ 对角线英寸。
"像素"这个词有两种含义:硬件上真实存在的物理像素,和开发者使用的抽象长度单位逻辑像素(CSS px / dp / pt)。两者的换算比率叫 DPR。
Retina 屏的做法是提高物理像素密度、同时保持逻辑尺寸不变,于是文字和矢量自动变清晰,而位图必须准备 @2x / @3x 才不糊。
实践底线三句话:图标用 SVG、布局用逻辑像素、位图配 srcset。