Web 平台 API / 响应式图片 · 让浏览器挑对那一张 / x 描述符与像素密度 待审核 2 / 10
srcset · 1x 2x 3x

x 描述符与像素密度

srcset 最早落地的部分是密度描述符。它回答的问题最简单:布局宽度已知且固定,只需要按屏幕的像素密度换一张更清晰的图。头像、图标、Logo、固定尺寸的商品缩略图都属于这一类。

1 · 语法与回退

<img src="avatar-96.png"
     srcset="avatar-96.png 1x, avatar-192.png 2x, avatar-288.png 3x"
     alt="作者头像">

srcset 是一个逗号分隔的候选列表,每项是 URL 加一个可选描述符。2x 表示「这张图按 2 倍密度绘制」,省略描述符等同于 1x。规范在解析时会删掉密度重复的后项,所以同一密度写两次不会报错,只是后一项永远不会被选中。

src 在这种写法里有两个身份。不认识 srcset 的浏览器只看 src;认识 srcset 的浏览器把 src 当作一个 1x 候选并入列表,前提是列表里还没有 1x 项、也没有任何 w 描述符。因此 srcset 里已经列了 1x 时,src 只剩回退一个用途,写成与 1x 同一个 URL 最省事。

2 · 密度改写天然尺寸

浏览器选中一张 2x 图后,会把它的像素宽除以 2 作为 naturalWidth。这一步叫 density-corrected natural size,它决定了图片在没有 CSS 宽度时的默认大小。

实测一张 200 × 200 的位图以 2x 候选被选中,naturalWidth 读回 100,渲染宽 100px;同一张图以 3x 被选中时同样是 100,因为那时它的位图是 300 宽。作者因此不必给 x 描述符的图写 width,它的 CSS 尺寸由 1x 的那一档决定。但仍然应当写:width / height 还承担下载前的占位(见 尺寸与占位),而天然尺寸要等图片下载完才知道。

图 2-1 · 一组 x 候选在本机上的选择结果,以及它在五档 DPR 下按参照规则的预测。可切换候选集与 src 回退,观察 naturalWidth 始终等于位图宽除以选中密度。

3 · 浏览器怎样取档

规范对这一步的表述只有一句:在候选中以 implementation-defined 的方式选择一个。它刻意不规定规则,让浏览器可以结合网络、缓存与用户设置决定。

作者原先的猜想是 Chromium 会在相邻两档之间做近似:DPR 1.5 的屏遇到 1.4x 与 3x 两档,1.4 离 1.5 只差 7%,而 3x 的像素数是它的 4.6 倍,取 1.4x 显然更划算。实测推翻了这个猜想。Chrome 154 在 DPR 1、1.5、2 三档下的行为完全一致:取密度不小于 DPR 的最小候选,全都不够时取最大的一张。DPR 1.5 下 1.4x / 3x 选 3x,1.2x / 1.8x 选 1.8x;DPR 2 下用 w 描述符换算出的 1.9 与 4 两档选 4。图 2-1 的预测一栏用的就是这条参照规则,与本机实测不符时会同屏显示两者(核对于 2026-09)。

警示 · 上述规则是 Chrome 154 在桌面、无网络限制下的实测,不是规范保证。规范明确允许浏览器在弱网或用户开启省流量时选更小的图,Safari 与 Firefox 本页未测。写候选列表时不应依赖「一定选中某一档」,而应让每一档本身都可接受(核对于 2026-09)。

选中之后,环境仍可能变化。规范鼓励浏览器在视口尺寸或页面缩放改变时重新执行选择,桌面 Chrome 的页面缩放会改变 devicePixelRatio:1x 屏上放大到 200%,DPR 读数变为 2。重新选择是可选的,且浏览器不会为了变小而换图,这一点在 w 描述符与 sizes §4 有实测。

4 · 适用边界

x 描述符只描述位图与 CSS 像素的比例,不描述图片在布局里有多宽。它隐含一个前提:所有屏幕上这张图的 CSS 宽度相同。一张在手机上宽 360px、在桌面上宽 720px 的流式图,用 x 描述符只能二选一地出错——按手机写,桌面 1x 屏拿到 1x 档后在 720px 宽里放大一倍;按桌面写,手机 2x 屏拿到的 2x 档是所需像素的 4 倍。

流式宽度需要 w 描述符,它把「布局有多宽」这一项单独交给 sizes 声明,下一页展开。同一个 srcset 里不应混用 x 与 w:规范要求有 sizes 时所有候选都带 w。但这是作者一侧的合规要求,解析器并不丢弃混写的项,而是把 w 项按 sizes 换算成密度后与 x 项放在同一条轴上比较。实测 DPR 1.5 下 a.png 3x, b.png 15w 配 sizes="10px" 选中 b.png,它换算出的 1.5 恰好不小于 DPR;这类写法能工作,却让读代码的人很难推断结果。