Web 平台 API / 响应式图片 · 让浏览器挑对那一张 / 一张图不够用:问题与来历 待审核 1 / 10
2010–2026

一张图不够用:问题与来历

<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。

图 1-1 · 同一张图在六种设备上的像素浪费。可调下发图宽,观察像素比按平方增长,以及图宽不足时哪些设备开始发虚。

第三个变量与前两个性质不同: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 · 参考文献

  1. Marcotte, E. (2010). Responsive Web Design. A List Apart, 2010-05-25.
  2. Marquis, M. (2012). Responsive Images: How they Almost Worked and What We Need. A List Apart, 2012-01.
  3. Marquis, M. (2014). Bringing Responsive Images to Browsers. A List Apart blog, 2014-04-01.
  4. Pieters, S. (2019). Add "auto sizes" for lazy-loaded images. whatwg/html issue #4654.