图片优化与自适应策略
1. 格式选择
| 格式 | 压缩 | 透明 | 动画 | 浏览器支持 | 适合 |
|---|---|---|---|---|---|
| AVIF | 最优(比 WebP 小 20-30%) | ✓ | ✓ | Chrome 85+, Firefox 93+ | 首选 |
| WebP | 优(比 JPEG 小 25-35%) | ✓ | ✓ | 全主流 | 通用 |
| JPEG | 中 | ✗ | ✗ | 全部 | fallback |
| PNG | 无损 | ✓ | ✗ | 全部 | 图标/截图 |
| SVG | 矢量 | ✓ | ✓ | 全部 | 图标/logo |
决策:AVIF > WebP > JPEG。用 <picture> 渐进增强。
2. <picture> 渐进增强
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero" width="1200" height="600"
loading="lazy" decoding="async">
</picture>
浏览器按顺序选第一个支持的格式。
3. 响应式图片(srcset + sizes)
<img
srcset="
hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w,
hero-1600.webp 1600w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
src="hero-800.webp"
alt="Hero"
width="1200"
height="600"
loading="lazy"
decoding="async"
/>
浏览器根据 viewport + 设备像素比选合适尺寸。
sizes 含义:
- 视口 ≤ 600px → 图片占满宽度(100vw)
- 视口 ≤ 1200px → 图片占一半(50vw)
- 更大 → 固定 800px
4. 懒加载
<!-- 首屏图不要 lazy(影响 LCP) -->
<img src="hero.webp" fetchpriority="high" alt="Hero">
<!-- 非首屏图 -->
<img src="thumb.webp" loading="lazy" decoding="async" alt="...">
loading="lazy" 原生支持。交叉观察器(IntersectionObserver)更灵活但一般不需要。
4.1 首屏 LCP 图片优化
<!-- preload + fetchpriority -->
<link rel="preload" as="image" href="hero.avif" type="image/avif"
imagesrcset="hero-400.avif 400w, hero-800.avif 800w"
imagesizes="100vw">
<img src="hero.avif" fetchpriority="high" alt="Hero"
width="1200" height="600">
LCP 图片不要懒加载 + 要 preload + 要 fetchpriority="high"。
5. CDN 图片处理
各 CDN 支持 URL 参数实时处理:
5.1 阿里云 OSS
https://bucket.oss-cn-hangzhou.aliyuncs.com/photo.jpg?x-oss-process=image/resize,w_800/format,webp/quality,Q_80
5.2 Cloudflare Images
https://example.com/cdn-cgi/image/width=800,format=auto,quality=80/photo.jpg
format=auto 根据 Accept 头自动选 AVIF / WebP / JPEG。
5.3 Imgix / Cloudinary
https://myapp.imgix.net/photo.jpg?w=800&auto=format&q=80
6. 构建时优化
6.1 sharp(Node)
npm i sharp
const sharp = require('sharp')
// 批量转 WebP + AVIF
await sharp('input.jpg')
.resize(800)
.webp({ quality: 80 })
.toFile('output.webp')
await sharp('input.jpg')
.resize(800)
.avif({ quality: 60 })
.toFile('output.avif')
6.2 Vite 插件
// vite.config.ts
import { viteImagemin } from 'vite-plugin-imagemin'
export default {
plugins: [
viteImagemin({
gifsicle: { optimizationLevel: 3 },
mozjpeg: { quality: 80 },
pngquant: { quality: [0.7, 0.9] },
webp: { quality: 80 },
avif: { quality: 60 },
})
]
}
6.3 Next.js Image
import Image from 'next/image'
<Image
src="/hero.jpg"
width={1200}
height={600}
alt="Hero"
priority // 首屏
placeholder="blur" // 模糊占位
blurDataURL="data:image/..."
/>
Next.js Image 自动:
- 按设备 srcset
- WebP / AVIF 自动协商
- 懒加载(非 priority)
- 防 CLS(必填 width/height)
7. 占位策略
防 CLS + 提升感知速度:
7.1 固定宽高
<img width="800" height="600" src="..." alt="...">
CSS aspect-ratio 等效:
img { aspect-ratio: 4/3; width: 100%; height: auto; }
7.2 LQIP(低质量占位)
构建时生成 10px 缩略图 base64:
<img src="full.webp" style="background: url(data:image/jpeg;base64,...) no-repeat center/cover">
7.3 BlurHash
import { encode } from 'blurhash'
// 编码为 4x3 的 hash 字符串
const hash = encode(imageData, 4, 3)
// 前端 decode 渲染 canvas
更小的占位表示。
7.4 CSS dominant color
<div style="background-color: #3a7bd5; aspect-ratio: 16/9;">
<img loading="lazy" src="..." alt="...">
</div>
8. SVG 优化
# SVGO 压缩
npx svgo input.svg -o output.svg
# 批量
npx svgo -f ./src/icons/ -o ./dist/icons/
// svgo.config.js
module.exports = {
plugins: [
'preset-default',
{ name: 'removeViewBox', active: false },
'removeDimensions',
]
}
9. 视频替代 GIF
GIF 体积巨大。用 <video> 替代:
<video autoplay loop muted playsinline>
<source src="animation.webm" type="video/webm">
<source src="animation.mp4" type="video/mp4">
</video>
WebM 通常比等效 GIF 小 80%+。
10. 性能预算
{
"size-limit": [
{ "path": "dist/images/**/*.{jpg,png,webp}", "limit": "500 KB" }
]
}
单页总图片传输 < 1MB(移动端)。
11. 监控
// 慢图片上报
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.initiatorType === 'img' && entry.duration > 2000) {
reportSlowImage({
url: entry.name,
size: entry.transferSize,
duration: entry.duration,
})
}
}
}).observe({ type: 'resource', buffered: true })
12. 常见反模式
- 首屏 LCP 图 lazy load:LCP 变慢
- 不设 width/height:CLS
- 2000px 图给 400px 容器:浪费带宽
- 不用 WebP / AVIF:多 30-50% 体积
- PNG 存照片:应该 JPEG/WebP
- GIF 动图:用 video 代替
- 图片不走 CDN:回源慢
- 不限最大尺寸:用户上传 10MB 原图
- 所有图 fetchpriority="high":等于没标
- SVG 不压缩:可能含几十 KB 无用 metadata