srcset、sizes 和 picture 如何实现响应式图片?
简化版
srcset 提供多个图片候选,sizes 告诉浏览器图片在不同视口下预计占多宽,浏览器结合设备像素比、网络等因素选择资源;picture 则用多个 source 做格式或美术方向切换。src 必须保留作默认和兼容回退,同时应设置尺寸避免布局偏移。
详细版
同一画面不同分辨率应使用 srcset 的宽度描述符,如 480w、960w,并用 sizes 描述布局槽位;固定显示尺寸的高密度图也可使用 1x、2x。最终选择权属于浏览器,开发者不能假定某一候选必被下载。
若小屏需要裁剪成竖图,或希望优先 AVIF、失败后回退 WebP/JPEG,应使用 <picture>。浏览器从上到下选择第一个媒体条件和 MIME 类型都支持的 <source>,内部 <img> 仍负责语义、替代文本、尺寸和加载行为。
图片优化还需配合正确压缩、width/height 或 aspect-ratio、首屏加载优先级和非首屏懒加载,不能只添加一组候选 URL。
完整版教学
一、响应式图片解决带宽和清晰度的矛盾
一张宽 2400px、大小 900KB 的横幅在手机上可能只显示 360px 宽,直接下载原图会浪费大量流量。反过来只提供 480px 图片,在桌面高密度屏上放大到 1000px 又会模糊。响应式图片让浏览器在“足够清晰”和“传输更小”之间选择。
CSS 槽位宽度 × 设备像素比 ≈ 所需资源像素宽度
360px × 2 DPR = 720px
浏览器还可能参考节流、缓存和自身策略,所以这个公式是选择依据而不是强制命令。开发者负责给合理候选,不应通过 JavaScript 猜设备并替换 URL。
二、宽度描述符必须与 sizes 配合
480w 表示资源自身约宽 480 像素,不代表它显示为 480px。sizes 则描述在媒体条件下图片占据的 CSS 槽位宽度,浏览器在布局 CSS 尚未全部完成前就能据此预加载合适资源。
<img
src="/photo-960.jpg"
srcset="/photo-480.jpg 480w,
/photo-960.jpg 960w,
/photo-1440.jpg 1440w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="海边日落"
width="1440"
height="900">
在 390px 宽、DPR 为 2 的手机上,槽位约为 390px,目标接近 780px,浏览器可能选择 960w。在 1200px 桌面上槽位是 600px,DPR 为 1 时也可能选择 960w,因为它是首个足够覆盖目标的候选。
三、密度描述符适合固定槽位
如果头像始终以 64 × 64 CSS px 显示,可以用 1x、2x、3x 表达不同设备像素密度。密度描述符与宽度描述符不能混在同一个 srcset 中。
<img
src="/avatar-64.png"
srcset="/avatar-64.png 1x, /avatar-128.png 2x, /avatar-192.png 3x"
width="64"
height="64"
alt="用户头像">
DPR 为 2 的屏幕通常选 128px 资源,正好用四个设备像素绘制一个 CSS 像素区域。若图片槽位会随布局显著变化,应回到 w + sizes,否则密度信息无法描述宽度变化。
四、picture 负责格式协商和美术方向
picture 本身不渲染图片,它给内部 img 提供候选来源。浏览器从上到下检查 source,适合用来优先选择 AVIF/WebP,或在窄屏展示重新裁剪的主体图。
<picture>
<source media="(max-width: 600px)" srcset="/hero-portrait.avif" type="image/avif">
<source srcset="/hero-wide.avif" type="image/avif">
<source srcset="/hero-wide.webp" type="image/webp">
<img src="/hero-wide.jpg" width="1600" height="900" alt="讲师在白板前授课">
</picture>
格式切换保持同一内容,只改变编码;美术方向切换会改变裁剪、构图甚至宽高比。后者必须确认各版本仍传达同样的 alt 语义,必要时为不同布局预留正确比例。
五、尺寸属性是防止 CLS 的关键
现代浏览器会根据 width 与 height 推导固有宽高比,在图片到达前预留空间。若一张最终高度 400px 的图片下载后突然把正文推下 400px,会造成明显累计布局偏移。
| 配置 | 下载前空间 | 风险 |
|---|---|---|
| 无尺寸信息 | 通常为 0 | 图片到达后跳动 |
width + height | 按比例预留 | 最稳定 |
CSS aspect-ratio | 按指定比例预留 | 适合容器驱动 |
| 只写固定高度 | 可能拉伸 | 需搭配宽度与 object-fit |
HTML 尺寸描述的是比例基础,CSS 仍可设置 max-width: 100%; height: auto 让图片响应式缩放。二者并不冲突。
六、首屏和非首屏的加载策略不同
首屏主图通常是 LCP 候选,应直接出现在 HTML 中,避免 loading="lazy",必要时使用 fetchpriority="high"。视口下方图片可用原生懒加载减少初始请求,但过度懒加载会让用户滚动时看到空白。
原图 900KB → AVIF 260KB,节省 640KB
4G 实际 1.6MB/s 时,纯传输时间约少 400ms
候选数量也不是越多越好,每多一个版本都增加构建和存储成本。常见做法是按真实布局挑 3~5 个关键宽度,再用性能数据检查命中和浪费情况。
记忆钩子:
srcset给菜单,sizes报桌子有多宽,浏览器点菜;picture则先决定今天进哪家餐厅。
七、常见误区与追问
- 误区:
srcset会让浏览器总是选择最接近的文件。 浏览器拥有最终策略,缓存、DPR、网络和节省流量设置都可能影响选择。 - 误区:写了
srcset就不需要src。src是默认候选和旧环境回退,也是img的基础资源。 - 误区:所有图片都应该懒加载。 首屏 LCP 图片懒加载会延后发现与请求,反而恶化性能。
- 追问:为什么
sizes写错会浪费流量? 浏览器会按错误槽位估算需求,例如实际50vw却写100vw,可能下载接近两倍宽的资源。 - 追问:CSS 背景图能用
picture吗? 不能直接使用,但可通过image-set()和媒体查询提供不同密度或格式候选。 - 追问:
alt应写文件内容还是用途? 应表达图片在当前上下文中的等价信息;纯装饰图片通常使用空alt=""。
八、加强记忆
先判断需求:同一构图只差清晰度,用 srcset;槽位随视口变化时再准确写 sizes;构图或编码格式需要切换,用 picture。用“槽位宽度乘 DPR”估算候选范围,例如 390 × 2 ≈ 780px,但把最终选择留给浏览器。最后补齐 src、替代文本和宽高比例,并把首屏 LCP 与下方图片分开制定加载策略,响应式图片才同时做到清晰、省流量、无跳动。