打开浏览器的网络面板,你会发现拖慢页面的常常不是 JS,而是图片。一张没处理过的 banner 可能就有两三 MB,而它往往还是首屏最大的元素,直接决定了 LCP(最大内容绘制)这个核心 Web 指标的成绩。
图片优化是性价比最高的前端优化之一。这篇给一套完整的实践清单。
第一步:选对格式
格式选错,后面再怎么优化都是事倍功半。现代格式在同等画质下能小一大截。
| 格式 | 特点 | 适用 |
|---|---|---|
| AVIF | 压缩率最高,支持透明和动画 | 首选,照片类能比 JPEG 小 50%+ |
| WebP | 压缩好,兼容性好 | 通用首选,AVIF 的稳妥替代 |
| JPEG | 有损,无透明 | 照片(没上现代格式时的兜底) |
| PNG | 无损,支持透明 | 图标、需要透明的图、线条图 |
| SVG | 矢量,任意缩放不失真 | 图标、Logo、简单插画 |
实践建议:
- 照片类:优先 AVIF / WebP,JPEG 兜底。
- 需要透明:WebP / AVIF / PNG。
- 图标、Logo:能用 SVG 就用 SVG,矢量、体积小、可无限缩放。
- 别用 PNG 存照片:无损格式存照片体积会非常大。
用 picture 做格式降级
不是所有浏览器都支持 AVIF。用 <picture> 让浏览器自动挑它支持的第一个格式:
<picture>
<source srcset="/img/cover.avif" type="image/avif" />
<source srcset="/img/cover.webp" type="image/webp" />
<img src="/img/cover.jpg" alt="封面" />
</picture>浏览器从上往下挑:支持 AVIF 用 AVIF,否则试 WebP,都不支持就用最后的 <img> 里的 JPEG。<img> 是必需的兜底,也是真正承载 alt、尺寸等属性的元素。
第二步:按屏幕提供合适尺寸
给手机用户推一张 2000px 宽的图,是巨大的浪费。用 srcset + sizes 让浏览器按设备挑合适尺寸。
<img
src="/img/cover-800.jpg"
srcset="
/img/cover-400.jpg 400w,
/img/cover-800.jpg 800w,
/img/cover-1600.jpg 1600w
"
sizes="(max-width: 600px) 100vw, 800px"
alt="封面"
/>怎么理解:
srcset里的400w是图片的实际像素宽度(不是显示宽度),告诉浏览器有哪些尺寸可选。sizes告诉浏览器这张图在不同条件下会以多大显示:屏幕 ≤600px 时占满视口(100vw),否则固定800px。- 浏览器结合
sizes、设备像素比(DPR)、网络情况,自己挑最合适的那张下载。
这样高分屏拿到高清图,小屏和低 DPR 设备拿到小图,流量和清晰度兼顾。
第三步:懒加载首屏外的图片
首屏之外的图片,没必要一开始就加载。现代浏览器原生支持 loading="lazy":
<img src="/img/photo.jpg" alt="" loading="lazy" />一个属性搞定,简单可靠,适合绝大多数场景。
但首屏的关键图片(尤其 LCP 元素)千万不要加 lazy,反而要让它尽快加载,甚至用 fetchpriority="high" 提高优先级:
<!-- 首屏 banner:高优先级,不要懒加载 -->
<img src="/img/hero.avif" alt="主图" fetchpriority="high" />需要更精细的控制(占位图、淡入动画、加载统计)时,可以用 IntersectionObserver 自己实现懒加载,但简单场景原生 loading="lazy" 就够了。
第四步:防止布局抖动(CLS)
图片加载完才撑开高度,会把下面的内容"顶"下去,造成布局抖动——这是 CLS(累积布局偏移)变差的主因,体验上就是"页面跳来跳去"。
解决办法:提前告诉浏览器图片的宽高比,让它预留空间。
最简单的是写上 width 和 height 属性(即使 CSS 会改变实际显示尺寸,浏览器也会用这两个值算出宽高比预留位置):
<img src="/img/photo.jpg" width="800" height="450" alt="" />或者用 CSS 的 aspect-ratio:
.cover {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover; /* 裁切填充,不变形 */
}只要预留了空间,图片加载前后位置就不会跳。
第五步:压缩与控制体积
即使格式选对了,也要压缩。
- 构建时压缩:用工具链(如
sharp、imagemin、各类打包插件)在构建阶段把图片转成 AVIF/WebP 并压缩,生成多种尺寸。 - 质量别拉满:照片类 JPEG/WebP 质量设到 75–85 通常肉眼无差,但体积小很多。
- 图标用 SVG 并优化:用 SVGO 去掉冗余,或做成 SVG sprite / icon font。
- 控制尺寸上限:别把原图(几千像素)直接挂上去,按实际显示需求生成。
如果用了 Nuxt / Next 这类框架,它们一般自带图片优化模块(如 Nuxt Image),能自动转格式、生成 srcset、按需裁剪,优先用框架能力,别自己造轮子。
第六步:善用 CDN 与缓存
图片是静态资源,应该长缓存。文件名带内容 hash,然后设强缓存:
Cache-Control: public, max-age=31536000, immutable这样用户二次访问直接命中本地缓存,根本不发请求。再配合 CDN 把图片分发到离用户近的节点,加载更快。很多图片 CDN 还支持 URL 参数实时裁剪、转格式,前端只管按需拼参数即可。
一份落地清单
实际项目里,按这个清单逐项检查:
- 照片用 AVIF/WebP,图标用 SVG,别用 PNG 存照片。
- 用
<picture>做格式降级。 - 用
srcset+sizes提供多尺寸,适配不同屏幕和 DPR。 - 首屏外图片加
loading="lazy";首屏 LCP 图不要懒加载,可加fetchpriority="high"。 - 给每张图写
width/height或aspect-ratio,避免 CLS。 - 构建时压缩、生成多尺寸,质量 75–85。
- 图片走 CDN + 强缓存(文件名带 hash)。
总结
图片优化没有什么高深技巧,但它实打实地影响 LCP、CLS 这些用户能感知到的指标。核心思路就三条:
- 更小:选现代格式 + 压缩。
- 更合适:按屏幕和 DPR 给对应尺寸。
- 更晚但不影响首屏:首屏外懒加载,首屏图提优先级,并预留空间防抖动。
把上面那份清单走一遍,大多数页面的图片加载体验都能有立竿见影的提升。