一张图不够用:问题与来历
<img src> 自 1993 年起就只接受一个 URL。这在所有屏幕都是 1x、页面都是固定 960px 宽的年代没有问题。2010 年前后两件事同时发生:5 月,Ethan Marcotte 在 A List Apart 发表 Responsive Web Design,流式栅格让同一张图的布局宽度随视口变化;6 月,iPhone 4 以 2 倍像素密度发售,同一个 CSS
像素背后变成了 4 个设备像素。一个 URL 从此要同时应付两个彼此独立的变量。
1 · 三个变量
第一个变量是像素密度,即 devicePixelRatio:一个 CSS 像素对应几个设备像素。布局宽度 300px 的图,在 1x 屏上需要 300px 宽的位图,在 3x 屏上需要 900px。
第二个变量是布局宽度。流式布局里,同一张图在 360px 宽的手机上占满一栏,在 1440px 的桌面上可能只占三分之一栏,两者所需的位图宽度都要再乘上密度。
两个变量相乘得到所需图宽,而下发的图宽超出它时,浪费按平方增长——宽和高两个方向同时放大。按桌面 2x 屏 1080px 栏宽准备的 2160px 图,在 360px 宽的 2x 手机上是所需像素的 9 倍;解码成 RGBA 位图后占 12.4 MB 内存,而所需的 720px 图只要 1.4 MB。
第三个变量与前两个性质不同:art direction,即不同屏幕需要不同的画面。横幅在桌面上是一张宽幅合影,缩到手机宽度后人脸只剩几像素高,此时需要的是另一张裁切过的图,而不是同一张图的缩小版。前两个变量只影响「下载多大」,第三个变量影响「下载哪一张」,这个区别后来决定了规范的分工。
2 · 2010–2014:srcset 与 picture 之争
规范出现之前的几年,社区的方案都绕开 HTML:服务器读 cookie 里由脚本写入的屏幕宽度再决定返回哪张图,或者页面先输出占位、再由脚本替换 src。前者在首次访问时没有 cookie,后者与浏览器的 preload scanner 冲突——HTML 解析器在脚本执行之前就已经按 src 发出了请求,替换等于多下载一次。
这个冲突是整个问题的核心约束。浏览器为了尽早开始下载,在 CSS 与布局就绪之前就要决定图片 URL,而那时它还不知道这张图在布局里有多宽。任何方案都必须让 HTML 本身携带足够的信息。
2012 年出现了两份互相竞争的提案。Mat Marquis 在 1 月的 A List Apart 上把问题摆上台面,2 月 W3C 成立了 Responsive Images Community Group(RICG),主推 <picture> 元素:借用 <video> 的 <source media="…"> 结构,按媒体条件切换来源。5 月,Apple 的
Edward O'Connor 在 WHATWG 邮件列表上提出 srcset 属性,WHATWG 编辑 Ian Hickson 随即将其写入规范。社区一方认为这绕过了他们半年的工作,争论随之公开化。
当时的 srcset 与今天同名不同义。它的 600w 表示「视口宽度不超过 600px 时」,是一个伪装成描述符的媒体查询;真正沿用至今的只有 2x 这类密度描述符。同年,WebKit 在 CSS 里先行实现了带 -webkit- 前缀的 image-set(),同样只支持 x。
6 月 18 日,媒体查询规范编辑 Florian Rivoal 提出折衷:两者都要,<picture> 负责按条件换图,srcset 以简化语法负责密度。这个方向在 2013 年 10 月又经历了一轮 Tab Atkins 与 John Mellor 的 srcN 提案,它在单一元素上覆盖全部用例,反响不一,但留下了一项关键设计:把 w
改为描述图片自身的像素宽,并用一个独立的 sizes 属性告诉浏览器布局宽度。
2014 年初,RICG 与 Tab Atkins 以 <picture> 的外形重写了规范,吸收了 srcN 的 w 与 sizes。剩下的问题是没人实现。RICG 在 4 月为 Yoav Weiss 发起众筹,一周多筹得一万美元以上,资助他在 Blink 中实现 <picture>。
3 · 2014–2026:逐项落地
下表按首个稳定版本排列各项能力(兼容性据 MDN BCD,核对于 2026-09)。
| 能力 | Chrome | Firefox | Safari |
|---|---|---|---|
srcset 的 x 描述符 |
34(2014-04) | 38(2015-05) | 8(2014-10) |
<picture>、sizes、w 描述符、currentSrc |
38(2014-10) | 38(2015-05) | 9.1(2016-03) |
decoding 属性、img.decode() |
65 / 64(2018) | 63 / 68(2018–2019) | 11.1(2018-04) |
<link rel=preload> 的 imagesrcset / imagesizes |
73(2019-03) | 78(2020-06) | 17.2(2023-12) |
loading="lazy" |
77(2019-09) | 75(2020-04) | 15.4(2022-03) |
width / height 映射为 aspect-ratio |
79(2019-12) | 71(2019-12) | 15(2021-09) |
<source> 的 width / height |
90(2021-04) | 108(2022-12) | 15(2021-09) |
fetchpriority |
101(2022-04) | 132(2024-10) | 17.2(2023-12) |
无前缀、支持 type() 的 image-set() |
113(2023-05) | 89(2021-06) | 17(2023-09) |
sizes="auto" |
126(2024-06) | 150(2026-04) | 27(2026-09) |
同一时期还有一条服务器侧的路线:Client Hints 让浏览器在请求头里携带 DPR、Width、Viewport-Width,由服务器决定返回哪张图。Chrome 46(2015-10)起实现,后在 Chrome 97 改名为 Sec-CH- 前缀;Firefox 与 Safari 至今均未实现(核对于 2026-09)。它适合图片 CDN
做透明优化,但不能替代 HTML 里的声明。
表里最后一行是本系列的主角之一。sizes 要求作者在 HTML 里重复写出 CSS 已经表达过的布局宽度,这正是 2012 年那条「布局就绪之前就要选图」约束的代价。loading="lazy" 落地后,屏外图片的下载本来就推迟到布局之后,约束对它们不再成立。Simon Pieters 在 2019 年 5 月据此提出
sizes="auto"(whatwg/html#4654),思路来自 lazySizes 库的 data-sizes="auto";规范 PR 在 2023 年 6 月合入,到 Safari 27 为止用了七年多才让三家引擎全部支持(核对于 2026-09)。
注 · 上表的 Safari 8 对 srcset 只支持 x 描述符,w 描述符与 sizes 要到 9.1 随 <picture> 一同到来;BCD 把两者分成两条记录,混为一谈会把 Safari 的 w 支持提早一年半。
4 · 规范的分工
合并后的设计把三个变量分给了不同的机制,本系列其余各页按这个分工展开:
- 像素密度与布局宽度交给
srcset,浏览器从候选里选。作者只陈述事实(每张图多宽、布局多宽),不写选择规则,浏览器可以结合网络状况、缓存与用户设置自行决定 - art direction 与图片格式交给
<picture>的<source>,按media与type强制切换,浏览器没有自由裁量,第一个匹配的<source>必须采用 - 下载时机、占位尺寸与优先级交给
loading、width/height、fetchpriority与decoding,它们不影响选哪一张,只影响何时下载、下载前占多大
「srcset 是提示、<source media> 是命令」这一区分贯穿始终。把分辨率切换写成 <source media="(min-width: 800px)"> 能工作,但剥夺了浏览器在弱网下降级、在缓存里复用大图的余地;推荐方案 一页会回到这一点。
5 · 参考文献
- Marcotte, E. (2010). Responsive Web Design. A List Apart, 2010-05-25.
- Marquis, M. (2012). Responsive Images: How they Almost Worked and What We Need. A List Apart, 2012-01.
- Marquis, M. (2014). Bringing Responsive Images to Browsers. A List Apart blog, 2014-04-01.
- Pieters, S. (2019). Add "auto sizes" for lazy-loaded images. whatwg/html issue #4654.