做完一个项目,Lighthouse跑分一看:性能40分。打开Network面板,图片占了三兆多。
图片优化是前端性能优化里性价比最高的部分,改完立竿见影。今天就聊聊怎么优化。
一、选对格式
| 格式 | 特点 | 适用 |
|---|---|---|
| JPEG | 有损压缩,不支持透明 | 照片、商品图 |
| PNG | 无损,支持透明 | 图标、需要透明的图 |
| WebP | 比JPEG小25-35% | 首选,现代浏览器都支持 |
| AVIF | 比WebP再小20-30% | 新项目可以试 |
一个实测数据:同一张商品图,JPEG 180KB,WebP 95KB,AVIF 68KB。
用法:
html
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="商品图片" loading="lazy">
</picture>
浏览器会自动选择支持的格式。老浏览器降级到JPEG,新浏览器用最小的那个。
二、压缩图片
macOS上可以用pngquant、jpegoptim,批量处理:
bash
# PNG压缩
for f in *.png; do pngquant --quality=65-80 "$f"; done
# JPEG压缩
for f in *.jpg; do jpegoptim --max=80 "$f"; done
Node生态有sharp:
javascript
const sharp = require('sharp');
async function compress(input, output) {
await sharp(input)
.resize({ width: 1200, withoutEnlargement: true })
.webp({ quality: 80 })
.toFile(output);
}
我做的一个项目,把30张商品图统一压到80质量、WebP格式,总体积从4.2MB降到1.1MB,首屏时间从3.8秒降到1.5秒。
三、响应式图片
不要给手机加载桌面端的大图。
html
<img
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w,
image-1600.jpg 1600w
"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
33vw"
src="image-800.jpg"
alt="示例">
sizes告诉浏览器在不同视口下的显示宽度,浏览器挑最合适的图下载。
Vue项目里可以封个组件:
vue
<template>
<img
:src="src"
:srcset="srcset"
:sizes="sizes"
:alt="alt"
:loading="loading"
>
</template>
<script setup>
const props = defineProps({
src: String,
alt: { type: String, default: '' },
widths: { type: Array, default: () => [400, 800, 1200] },
sizes: { type: String, default: '100vw' },
loading: { type: String, default: 'lazy' }
});
const srcset = computed(() =>
props.widths.map(w => `${props.src}?w=${w} ${w}w`).join(', ')
);
</script>
配合图片CDN的动态处理,一张原图能生成任意尺寸。
四、懒加载
首屏之外的图片延后加载。
html
<!-- 原生方案,最简单 -->
<img src="photo.jpg" loading="lazy" alt="图片">
兼容性问题可以用IntersectionObserver自己实现:
javascript
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
}, {
rootMargin: '100px' // 提前100px开始加载
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
注意:首屏的图片不要懒加载 ,会影响LCP指标。首屏图片加loading="eager"和fetchpriority="high"。
五、占位与骨架
图片加载前留出空间,避免布局抖动(CLS)。
css
.img-wrapper {
aspect-ratio: 16 / 9; /* 预设比例,图片加载完也不会跳动 */
background: #f5f5f5;
overflow: hidden;
}
.img-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
}
aspect-ratio是解决CLS的利器,比padding-top的hack方案优雅得多。
六、关键图片预加载
html
<link rel="preload" as="image" href="hero.webp">
首屏大图预加载,能明显改善LCP。
七、CDN + 缓存
nginx
location ~* \.(jpg|jpeg|png|webp|avif)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
图片配合CDN,二次访问直接从边缘节点拿,几乎零延迟。
优化前后的对比
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 图片总体积 | 4.2MB | 1.1MB |
| 首屏加载 | 3.8s | 1.5s |
| LCP | 4.1s | 1.4s |
| CLS | 0.24 | 0.02 |
落地顺序建议
- 先转WebP/AVIF,成本最低
- 再统一压缩质量到80
- 加懒加载
- 用
aspect-ratio消除抖动 - 最后接CDN
按这个顺序做,每一步都有明显收益。