picture 与 source:换图与格式协商
srcset 把「选哪一张」交给浏览器裁量,浏览器可以因为网络差而选小图、因为缓存里已有而选大图。有些场合作者不能让出这个裁量:手机上必须换成裁切过的方图,否则主体只剩几像素高;浏览器不支持 AVIF 时必须换成 JPEG,否则什么也显示不出来。<picture> 承接的就是这两类强制切换。
1 · 结构与求值顺序
<picture>
<source media="(min-width: 900px)" srcset="hero-wide.avif" type="image/avif">
<source media="(min-width: 900px)" srcset="hero-wide.jpg">
<source srcset="hero-square.avif" type="image/avif">
<img src="hero-square.jpg" width="800" height="800" alt="发布会现场">
</picture>
<picture> 本身不渲染任何东西,也没有自己的尺寸。真正显示的始终是末尾那个 <img>:alt、loading、decoding、fetchpriority、CSS 样式与事件监听都写在它身上,currentSrc 也从它读。<source>
只是给 <img> 提供候选来源的列表,没有 <img> 的 <picture> 什么也不显示。
浏览器按文档顺序检查每个 <source>:media 为假、type 不支持、srcset 为空或解析不出候选的都跳过,第一个通过的 <source> 胜出,它的 srcset 与 sizes 成为 <img> 的候选集,之后的选择与单独的
<img srcset> 相同。全部跳过时才轮到 <img> 自己的 srcset / src。<img> 上即使写了 media 也不参与判断——这个属性只属于 <source>。
2 · art direction
media 解决的是 问题与来历 §1 的第三个变量:不同屏幕需要不同的画面。它与 sizes 里的媒体条件语法相同,含义却不同。sizes 里的条件只改变「布局宽度」这一个输入,最终选哪张仍由浏览器裁量;<source media>
的条件一旦为真,就只能从这个 <source> 的候选里选。
不同裁切通常有不同的宽高比,占位尺寸也要跟着变。<source> 的 width / height 属性就是为此加的(Chrome 90、Firefox 108、Safari 15,兼容性据 MDN BCD,核对于 2026-09):某个 <source> 胜出时,<img> 改用它的宽高计算占位比例。只在
<img> 上写方图的 800 × 800 时,实测视口 1000px 下命中 8:3 宽幅分支,图片解码前的占位仍是正方形,加载完成后盒子才塌成 8:3,下方内容随之上移。每个 <source> 写上自己的宽高后,加载前后的比例一致。
media 分支,宽高比分别为 8:3、3:2 与 1:1。可调 iframe 视口宽观察命中分支,切换「只写 img」观察宽幅分支在加载前按方图占位。3 · 格式协商
type 写的是 MIME 类型,浏览器不支持这个类型就跳过该 <source>。这让新格式可以渐进部署:AVIF 在前、WebP 次之、JPEG 兜底,每个浏览器拿到自己能解的最好的那种。
判断只发生在 MIME 字符串上。浏览器不会先下载文件再看它是什么格式,那样就失去了在请求前做选择的意义。实测 Chrome 154 对 image/avif、image/webp、image/png 与 image/svg+xml 选中 <source>,对 image/jxl、image/heic
与一个虚构的 image/x-unknown 跳过。图 3-1 里每一行挂的文件都是 PNG,image/avif 那一行照样显示正常:类型一旦通过,文件内容由解码器按实际字节嗅探,与 type 的声明无关。反过来,type 写错(比如给 AVIF 文件标成
image/webp)不会报错,浏览器会按错误的标签选中,只在解码器不认识实际格式时显示为破图(核对于 2026-09)。
<source type> 判定。每行的文件都是 PNG,只有 type 的字面值不同。
注 · Chrome 154 不认 image/jxl。JPEG XL 在 Chromium 里的支持经历过加入、移除与重新讨论,Safari 17 起已支持(本页未在 Safari 上实测)。这是 type 协商的典型用途:用一行
<source type="image/jxl"> 给支持的浏览器新格式,其余浏览器无感跳过(核对于 2026-09)。
4 · 与 srcset 的分工
<picture> 能做分辨率切换,但不应该用来做。下面两种写法在 2x 屏上可能给出相同的结果:
<!-- ✗ 用 media 做分辨率切换 -->
<picture>
<source media="(min-resolution: 2dppx)" srcset="photo-1280.jpg">
<img src="photo-640.jpg" alt="…">
</picture>
<!-- ✓ 用 srcset 陈述候选 -->
<img srcset="photo-640.jpg 640w, photo-1280.jpg 1280w" sizes="…" src="photo-640.jpg" alt="…">
前者把「2x 屏必须拿 1280」写成了命令:弱网下浏览器不能降级,缓存里已有 1280 的 1x 屏也不能复用。后者只陈述事实,浏览器按 w 描述符与 sizes §4 描述的方式自行取舍。判据是:换的是不是另一张画面、另一种格式。是,用 <source>;只是同一画面的不同尺寸,放进
srcset。
<source> 同样可以用 sizes="auto"。条件与 <img> 相同,由末尾的 <img> 是否 allows auto-sizes 决定;满足时 <source> 的 sizes 可以整个省略,等同于 auto。实测
<img loading="lazy" sizes="auto" width="300" height="200"> 前面放一个不写 sizes 的 <source>,选中的是按 300px 算的 320w,而不是 100vw 对应的 1280w。