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 知识体系总览

下面这张图帮你理清今天讲的所有内容:

响应式图片技术选型 响应式图片 srcset + sizes 同一图片不同尺寸 srcset + x 描述符 根据 DPR 选择清晰度 <picture> 元素 不同裁切 / 格式降级 选型建议 优先使用 srcset + sizes 需要 DPR 控制时 srcset + x 描述符 复杂场景 使用 <picture> 💡 不要忘记加 fallback src 属性,兼容老旧浏览器 高 DPR 图也要控制文件体积,避免过度加载

4.6 实战中的避坑指南

讲几个我实际踩过的坑,你遇到了可以少走弯路。

  • sizes 写错导致加载大图。 有一次我把 100vw 写成了 100w,浏览器直接忽略,加载了最大图。后来我每次写完都会在 DevTools 里检查 Network 面板,确认加载的是预期尺寸。
  • 图片裁切点没对齐。 用 <picture> 做不同裁切时,要保证视觉焦点一致。我曾经把手机版裁切中心偏左,结果产品图的主体被切掉一半。后来我习惯用 object-position 配合 object-fit 做二次微调。
  • WebP 降级没测试。 有些旧版 Safari 不支持 WebP,但 <picture> 的 fallback 写对了就没问题。我建议你在 BrowserStack 上至少测三个版本:最新 Chrome、最新 Safari、旧版 Edge。

重要提醒:不要为了「省事」把所有图片都套上 <picture>。每个 <source> 浏览器都会去检查,虽然不会全部下载,但解析成本是有的。对于简单的尺寸适配,srcset 完全够用。

好了,响应式图片的核心内容就这些。记住一个原则:给合适的设备,发合适的图。 别让用户为你看不到的像素买单。