图片优化完全指南
图片通常占页面流量的 60-80%。优化图片是最高效的性能优化方式,能获得立竿见影的效果。
1. 图片格式选择
格式对比矩阵
| 格式 | 压缩率 | 透明度 | 动画 | 兼容性 | 最佳用途 |
|---|---|---|---|---|---|
| JPEG | ⭐⭐⭐⭐ | ❌ | ❌ | 100% | 照片 |
| PNG | ⭐⭐ | ✅ | ❌ | 100% | 图标、截图 |
| WebP | ⭐⭐⭐⭐⭐ | ✅ | ✅ | 93% | 所有图片 |
| AVIF | ⭐⭐⭐⭐⭐⭐ | ✅ | ✅ | 70% | 高端浏览器 |
| SVG | 可变 | ✅ | ✅ | 98% | 矢量图 |
| GIF | ⭐ | ✅ | ✅ | 100% | 不推荐 |
现实数据对比
JPEG 照片 (原始): 2.5MB
├─ 优化后 JPEG: 800KB (68% ↓)
├─ WebP 格式: 300KB (88% ↓)
├─ AVIF 格式: 150KB (94% ↓)
└─ 加载时间 (3G):
原始: 20s → 优化后 JPEG: 6.4s → WebP: 2.4s → AVIF: 1.2s
2. 现代图片标记
使用 picture + source 实现渐进增强
<!-- 最佳实践: 先新格式,后回退格式 -->
<picture>
<!-- AVIF: 最优压缩,但浏览器支持较少 -->
<source srcset="/image.avif" type="image/avif">
<!-- WebP: 很好的压缩,广泛支持 -->
<source srcset="/image.webp" type="image/webp">
<!-- JPEG: 降级方案,所有浏览器支持 -->
<img src="/image.jpg" alt="描述" loading="lazy">
</picture>
<!-- 响应式图片: 针对不同屏幕尺寸 -->
<picture>
<source
media="(min-width: 1200px)"
srcset="/image-large.avif, /image-large-2x.avif 2x"
type="image/avif"
>
<source
media="(min-width: 768px)"
srcset="/image-medium.webp, /image-medium-2x.webp 2x"
type="image/webp"
>
<img
srcset="/image-small.jpg, /image-small-2x.jpg 2x"
src="/image-small.jpg"
alt="响应式图片"
loading="lazy"
>
</picture>
<!-- 简化版: 使用 srcset 自适应 -->
<img
srcset="
/photo-480w.jpg 480w,
/photo-800w.jpg 800w,
/photo-1200w.jpg 1200w
"
sizes="(max-width: 600px) 100vw, 50vw"
src="/photo-800w.jpg"
alt="响应式照片"
>
3. 图片压缩最佳实践
压缩工具链
# 1. ImageMagick - 命令行工具
convert input.jpg -quality 80 output.jpg
convert input.jpg -resize 1920x1080 output.jpg
# 2. FFmpeg - 视频和图片
ffmpeg -i input.jpg -q:v 5 output.jpg
# 3. 专业工具
# Squoosh (Google 在线工具)
# TinyPNG (有损压缩)
# OptiPNG (无损 PNG)
Node.js 自动化方案
// sharp - 高性能图片处理库
import sharp from 'sharp'
async function optimizeImage(inputPath: string, outputDir: string) {
const image = sharp(inputPath)
// 获取图片信息
const metadata = await image.metadata()
console.log(`原始: ${metadata.width}x${metadata.height}, 格式: ${metadata.format}`)
// 1. 生成 WebP
await image
.resize(1920, 1080, { fit: 'inside', withoutEnlargement: true })
.webp({ quality: 80 })
.toFile(`${outputDir}/image.webp`)
// 2. 生成 AVIF
await image
.resize(1920, 1080, { fit: 'inside', withoutEnlargement: true })
.avif({ quality: 60 })
.toFile(`${outputDir}/image.avif`)
// 3. 生成优化的 JPEG
await image
.resize(1920, 1080, { fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 75, progressive: true })
.toFile(`${outputDir}/image.jpg`)
// 4. 生成缩略图
await image
.resize(400, 300, { fit: 'cover' })
.webp({ quality: 70 })
.toFile(`${outputDir}/image-thumb.webp`)
console.log('图片优化完成!')
}
// 批量处理
import fs from 'fs'
import path from 'path'
async function batchOptimize(inputDir: string, outputDir: string) {
const files = fs.readdirSync(inputDir)
for (const file of files) {
if (/\.(jpg|jpeg|png)$/i.test(file)) {
const inputPath = path.join(inputDir, file)
console.log(`处理: ${file}`)
await optimizeImage(inputPath, outputDir)
}
}
}
4. 懒加载实现
原生 Intersection Observer
// 方式 1: 原生实现,无依赖
function initLazyLoading() {
const images = document.querySelectorAll('img[data-src]')
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement
const src = img.dataset.src
if (src) {
// 使用 Blob 避免跨域问题
fetch(src)
.then(res => res.blob())
.then(blob => {
img.src = URL.createObjectURL(blob)
img.removeAttribute('data-src')
observer.unobserve(img)
})
.catch(err => console.error('加载失败:', err))
}
}
})
}, {
rootMargin: '50px' // 提前 50px 开始加载
})
images.forEach(img => observer.observe(img))
}
// 使用
// HTML: <img data-src="/image.jpg" alt="...">
框架集成
// React 自定义 Hook
import { useEffect, useRef, useState } from 'react'
function useLazyImage(src: string, placeholder?: string) {
const [imageSrc, setImageSrc] = useState(placeholder)
const [imageRef, setImageRef] = useState<HTMLImageElement | null>(null)
useEffect(() => {
if (!imageRef) return
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) {
const img = new Image()
img.onload = () => {
setImageSrc(src)
observer.unobserve(imageRef)
}
img.onerror = () => {
console.error('图片加载失败:', src)
}
img.src = src
}
},
{ rootMargin: '50px' }
)
observer.observe(imageRef)
return () => observer.disconnect()
}, [imageRef, src])
return [imageSrc, setImageRef] as const
}
// 使用
function ImageComponent({ src, placeholder }: { src: string; placeholder: string }) {
const [imageSrc, imageRef] = useLazyImage(src, placeholder)
return (
<img
ref={imageRef}
src={imageSrc}
alt="..."
style={{ minHeight: '300px' }}
/>
)
}
// Vue 3 指令
const vLazy = {
mounted(el: HTMLImageElement, { value: src }) {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
el.src = src
observer.unobserve(el)
}
}, { rootMargin: '50px' })
observer.observe(el)
}
}
// 使用: <img v-lazy="'/image.jpg'" :src="placeholder" />
带渐进加载的懒加载
// 先加载低质量占位符,再加载高质量图片
function ProgressiveImageLoad(src: string) {
return new Promise((resolve, reject) => {
// 步骤 1: 加载模糊占位符
const placeholder = new Image()
placeholder.onload = () => {
console.log('占位符加载完成,显示模糊图片')
}
placeholder.src = `${src}?w=20&q=10` // 超小尺寸,高压缩
// 步骤 2: 加载高质量图片
const highRes = new Image()
highRes.onload = () => {
console.log('高质量图片加载完成,替换占位符')
resolve(highRes.src)
}
highRes.onerror = reject
highRes.src = src // 原始高质量
})
}
// HTML 实现
const template = `
<!-- 显示模糊占位符 -->
<img
src="/image.jpg?w=20&q=10"
alt="..."
style="filter: blur(10px); transition: filter 0.3s;"
>
<!-- 加载完成后替换为高质量图片 -->
<img
src="/image.jpg"
alt="..."
loading="lazy"
onload="this.style.opacity='1'"
style="opacity: 0; transition: opacity 0.3s;"
>
`
5. CDN 优化
CDN 提供商能力
// 主流 CDN 提供商的图片优化功能
interface CDNImageOptimization {
// Cloudflare Image Optimization
cloudflare: {
// ?format=auto - 自动选择最优格式
// ?quality=75 - 质量控制 (1-100)
// ?width=400&height=300 - 尺寸调整
// ?fit=scale - 缩放方式
url: 'https://example.com/image.jpg?format=auto&quality=75&width=400'
},
// Imgix
imgix: {
// ?auto=format - 自动格式选择
// ?q=75 - 质量
// ?w=400&h=300 - 尺寸
// ?fit=crop - 裁剪
url: 'https://example.imgix.net/image.jpg?auto=format&q=75&w=400'
},
// AWS CloudFront + Lambda@Edge
aws: {
// 使用 Lambda 函数动态处理
// 支持自定义转换逻辑
url: 'https://d123.cloudfront.net/image.jpg'
}
}
自建 CDN 优化
// Node.js 后端实现图片处理 CDN
import express from 'express'
import sharp from 'sharp'
import cache from 'redis'
const app = express()
const redis = cache.createClient()
app.get('/image/:filename', async (req, res) => {
const { filename } = req.params
const { width, height, quality, format } = req.query
// 生成缓存 key
const cacheKey = `img:${filename}:${width}:${height}:${quality}:${format}`
// 检查缓存
const cached = await redis.get(cacheKey)
if (cached) {
res.set('X-Cache', 'HIT')
res.set('Content-Type', `image/${format || 'webp'}`)
return res.send(Buffer.from(cached))
}
try {
// 加载原始图片
let processor = sharp(`/images/${filename}`)
// 应用转换
if (width || height) {
processor = processor.resize(
parseInt(width as string) || undefined,
parseInt(height as string) || undefined,
{ withoutEnlargement: true }
)
}
// 选择格式
const targetFormat = format || 'webp'
if (targetFormat === 'webp') {
processor = processor.webp({ quality: parseInt(quality as string) || 80 })
} else if (targetFormat === 'avif') {
processor = processor.avif({ quality: parseInt(quality as string) || 60 })
} else {
processor = processor.jpeg({ quality: parseInt(quality as string) || 80 })
}
// 处理图片
const buffer = await processor.toBuffer()
// 缓存结果 (7 天)
await redis.setex(cacheKey, 7 * 24 * 60 * 60, buffer.toString('base64'))
// 设置响应头
res.set('X-Cache', 'MISS')
res.set('Content-Type', `image/${targetFormat}`)
res.set('Cache-Control', 'public, max-age=31536000') // 1 年缓存
res.send(buffer)
} catch (error) {
res.status(404).send('图片未找到')
}
})
6. 性能监控
// 监控图片加载性能
function monitorImagePerformance() {
// 方法 1: 使用 PerformanceObserver
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes('image')) {
const loadTime = entry.responseEnd - entry.startTime
const size = entry.transferSize
console.log(`图片: ${entry.name}`)
console.log(` 加载时间: ${loadTime.toFixed(2)}ms`)
console.log(` 传输大小: ${(size / 1024).toFixed(2)}KB`)
// 上报到分析服务
if (loadTime > 1000) {
reportSlowImageLoad(entry.name, loadTime)
}
}
}
})
observer.observe({ entryTypes: ['resource'] })
}
// 方法 2: 手动测量
async function measureImageLoad(src: string): Promise<{ time: number; size: number }> {
const start = performance.now()
const response = await fetch(src)
const size = parseInt(response.headers.get('content-length') || '0')
const end = performance.now()
return {
time: end - start,
size
}
}
// 方法 3: 计算 LCP (Largest Contentful Paint)
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries()
const lastEntry = entries[entries.length - 1]
console.log('LCP:', {
element: lastEntry.element?.tagName,
url: lastEntry.url,
startTime: lastEntry.startTime,
renderTime: lastEntry.renderTime
})
})
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] })
7. 完整优化清单
// ✅ 图片优化检查清单
// 1. 格式选择
// ✅ 照片 → JPEG 或 WebP/AVIF
// ✅ 图标 → PNG 或 SVG
// ✅ 动画 → WebP 或 AVIF
// ❌ 避免 GIF
// 2. 大小优化
// ✅ 照片压缩: 质量 75-85
// ✅ WebP: 质量 75-80
// ✅ AVIF: 质量 55-65
// ✅ 响应式: 480w, 800w, 1200w
// 3. 懒加载
// ✅ 首屏图片: 正常加载
// ✅ 非首屏: 使用 loading="lazy"
// ✅ 关键图片: 预加载 link rel="preload"
// 4. CDN
// ✅ 使用 CDN 分发
// ✅ 启用 Gzip 压缩
// ✅ 设置适当的缓存头
// 5. 监控
// ✅ 监控加载时间
// ✅ 监控传输大小
// ✅ 监控 LCP 指标
// 6. 工具链
// ✅ sharp 或 ImageMagick 自动处理
// ✅ 集成 CI/CD 流程
// ✅ 生成多格式版本
8. 优化效果统计
真实案例: 电商网站产品列表
优化前:
├─ 50 张产品图片
├─ 每张 JPEG: 平均 2MB
├─ 总大小: 100MB
├─ 加载时间: 45 秒
优化后:
├─ WebP 格式 + 压缩质量 80
├─ 每张 WebP: 平均 400KB
├─ 总大小: 20MB (80% ↓)
├─ 懒加载: 仅加载可见范围
├─ 首屏加载: 15 张图 → 6MB
├─ 加载时间: 8 秒 (82% ↓)
用户体验改善:
✅ 首屏时间快 5.6 倍
✅ 总流量节省 80%
✅ 移动设备体验明显改善
✅ 服务器带宽成本降低 80%
总结
图片优化的优先级:
| 优先级 | 优化方案 | 效果 | 难度 |
|---|---|---|---|
| 🔴 高 | 格式转换 (JPEG → WebP) | 60-70% | 低 |
| 🔴 高 | 图片压缩质量调整 | 40-50% | 低 |
| 🟠 中 | 懒加载 | 30-40% | 中 |
| 🟠 中 | 响应式图片 | 20-30% | 中 |
| 🟡 低 | CDN 优化 | 10-20% | 高 |


