4. 图片加载机制
说到响应式图片,很多同学第一反应就是「给图片设个 max-width: 100% 不就完事了?」
嗯,这确实能让图片不溢出容器。但你想想看——一个 2000px 宽的 banner 图,硬塞到 320px 宽的手机屏幕上,下载了 2MB 的数据,实际只显示了巴掌大一块。这浪费的不只是流量,还有页面加载性能。
我在项目中遇到过好几次这种情况:明明首屏加载速度还行,一审计发现图片体积占了总资源的 70% 以上。说白了,就是没做响应式图片处理。
核心思路:根据设备的屏幕尺寸、分辨率、像素比,给不同设备下发最合适的图片资源。不浪费带宽,也不牺牲清晰度。
4.1 srcset 与 sizes:让浏览器自己选
先看一个最简单的场景。你有一张图,准备了三个尺寸:
- small.jpg — 400px 宽,适合手机
- medium.jpg — 800px 宽,适合平板
- large.jpg — 1200px 宽,适合桌面
用 srcset 告诉浏览器有哪些候选图,用 sizes 告诉浏览器图片在布局中实际占多大。
<img
src="fallback.jpg"
srcset="
small.jpg 400w,
medium.jpg 800w,
large.jpg 1200w
"
sizes="
(max-width: 480px) 100vw,
(max-width: 960px) 50vw,
33vw
"
alt="产品展示图"
>
这里有个关键点:w 单位表示图片的真实宽度,不是文件大小。浏览器拿到这些信息后,会结合当前设备的视口宽度、像素比,自己算出一个最优资源。
我个人习惯:永远保留一个 src 作为 fallback。老浏览器不认识 srcset 时,至少还能显示一张图。别指望所有用户都用最新版 Chrome。
4.2 根据设备像素比加载不同图片
同样是 400px 宽的图片,在 1x 屏幕和 3x 屏幕上,需要的图片清晰度完全不同。
你可以在 srcset 里用 x 描述符来区分:
<img
src="photo-1x.jpg"
srcset="
photo-1x.jpg 1x,
photo-2x.jpg 2x,
photo-3x.jpg 3x
"
alt="高清产品图"
>
浏览器会根据设备的 devicePixelRatio 自动选择。比如 iPhone 的 DPR 是 3,它就会下载 photo-3x.jpg。
我曾经踩过一个坑:给 2x 和 3x 准备的图片尺寸一样,只是压缩率不同。结果 3x 图体积是 2x 的两倍多,但肉眼根本看不出区别。后来我学乖了——高 DPR 图也要控制文件大小,不是无脑上最高清。
4.3 picture 元素:更精细的场景控制
srcset 虽然好用,但它有个限制:只能换尺寸,不能换格式或裁切方式。
举个例子。桌面端展示一张横版全景图,手机端展示同一张图的竖版裁切。这种场景下,picture 元素就派上用场了。
<picture>
<source
media="(max-width: 480px)"
srcset="photo-mobile.jpg"
>
<source
media="(max-width: 960px)"
srcset="photo-tablet.jpg"
>
<source
media="(min-width: 961px)"
srcset="photo-desktop.jpg"
>
<img src="photo-fallback.jpg" alt="响应式图片">
</picture>
每个 <source> 可以独立指定 media 条件和 srcset。浏览器从上往下匹配,找到第一个符合条件的就加载。
还有一个常见用法:格式降级。比如优先用 WebP,不支持就回退到 JPEG。
<picture>
<source
type="image/webp"
srcset="photo.webp"
>
<source
type="image/jpeg"
srcset="photo.jpg"
>
<img src="photo.jpg" alt="格式降级示例">
</picture>
注意:<picture> 里的 <img> 不能省略。它既是 fallback,也是实际渲染图片的 DOM 元素。所有 CSS 样式、alt 属性都写在 <img> 上。
4.4 三种方案的对比与选择
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
srcset + sizes |
同一张图,不同尺寸 | 浏览器自动决策,代码简洁 | 无法切换裁切方式或格式 |
srcset + x |
根据 DPR 选图 | 精准匹配设备像素比 | 不考虑视口宽度变化 |
<picture> |
不同裁切、不同格式 | 完全控制,支持格式降级 | 代码量较大,维护成本高 |
我个人建议:能用 srcset 解决的,就别上 <picture>。少一层嵌套,少一份心智负担。
4.5 知识体系总览
下面这张图帮你理清今天讲的所有内容:
4.6 实战中的避坑指南
讲几个我实际踩过的坑,你遇到了可以少走弯路。
- sizes 写错导致加载大图。 有一次我把
100vw写成了100w,浏览器直接忽略,加载了最大图。后来我每次写完都会在 DevTools 里检查 Network 面板,确认加载的是预期尺寸。 - 图片裁切点没对齐。 用
<picture>做不同裁切时,要保证视觉焦点一致。我曾经把手机版裁切中心偏左,结果产品图的主体被切掉一半。后来我习惯用object-position配合object-fit做二次微调。 - WebP 降级没测试。 有些旧版 Safari 不支持 WebP,但
<picture>的 fallback 写对了就没问题。我建议你在 BrowserStack 上至少测三个版本:最新 Chrome、最新 Safari、旧版 Edge。
重要提醒:不要为了「省事」把所有图片都套上 <picture>。每个 <source> 浏览器都会去检查,虽然不会全部下载,但解析成本是有的。对于简单的尺寸适配,srcset 完全够用。
好了,响应式图片的核心内容就这些。记住一个原则:给合适的设备,发合适的图。 别让用户为你看不到的像素买单。