图片优化
概述
图片是网页内容的重要组成部分,研究表明图片可以占据网页总传输量的 50% 以上。一张未经优化的图片可能达到数 MB,下载这样一张图片的时间足以让用户放弃等待。更糟糕的是,如果 LCP(最大内容绘制)元素是一张图片,图片加载的快慢直接影响着用户感知到的页面加载速度。
图片优化的挑战在于多个维度的平衡:体积与质量的平衡——更小的文件通常意味着更低的视觉质量;格式选择的复杂性——WebP、AVIF、JPEG、PNG、SVG 各有优劣,没有万能最优解;响应式需求——用户可能在 320px 宽的手机或 2560px 宽的显示器上访问同一个页面。
一个完整的图片优化策略涵盖了从图片格式选择、压缩配置、响应式图片实现,到 CDN 集成、加载策略优化的完整链路。本节将系统讲解这些内容,帮助你构建适合自己项目的图片优化方案。
目标
- 掌握现代图片格式(WebP、AVIF)的特性与适用场景
- 理解响应式图片的实现方案与
<picture>元素 - 学会配置图片 CDN 与自动化优化流水线
- 掌握 LCP 图片的专项优化策略
知识体系
1. 图片格式选择
选择正确的图片格式是优化的第一步。不同格式有不同的压缩特性、支持的色彩范围和功能,选择不当会影响图片质量和体积。
格式对比
| 格式 | 压缩类型 | 透明度 | 动画 | 浏览器支持 | 适用场景 |
|---|---|---|---|---|---|
| JPEG | 有损 | ❌ | ❌ | 全部 | 照片、复杂图像 |
| PNG | 无损 | ✅ | ❌ | 全部 | 图标、截图、需要透明度 |
| WebP | 有损/无损 | ✅ | ✅ | >96% | 通用替代 JPEG/PNG |
| AVIF | 有损/无损 | ✅ | ✅ | >92% | 高压缩率照片 |
| SVG | 矢量 | ✅ | ✅ | 全部 | 图标、Logo、插画 |
JPEG 使用有损压缩,压缩率高,适合照片等复杂图像,但不支持透明度和动画。PNG 是无损压缩,支持透明度,适合需要清晰边缘的图像(如图标、截图),但文件较大。WebP 在相同质量下比 JPEG 小 25-35%,同时支持透明度和动画,是目前最推荐的通用图片格式。AVIF 基于 AV1 视频编码,压缩率比 WebP 更高(同样质量下体积更小),但编码速度较慢。SVG 是矢量格式,文件极小且无限缩放,适合图标和简单图形。
格式选择决策
图片格式选择:
┌─────────────────┐
│ 是矢量图形吗? │
└───┬─────────────┘
Yes → SVG
No ↓
┌─────────────────┐
│ 需要动画吗? │
└───┬─────────────┘
Yes → WebP(动画)/ AVIF
No ↓
┌─────────────────┐
│ 是照片/复杂图像? │
└───┬─────────────┘
Yes → AVIF > WebP > JPEG
No ↓
┌─────────────────┐
│ 需要透明度? │
└───┬─────────────┘
Yes → WebP > PNG
No → WebP > JPEG
这个决策树只是一个起点,实际选择还需要考虑具体场景。比如对于用户头像这样的小图,PNG 可能是更好的选择,因为 JPEG 的有损压缩在低分辨率下会明显降低清晰度。
2. 图片压缩
构建时压缩
现代前端项目通常使用构建工具在打包时自动压缩图片:
// vite.config.js
import viteImagemin from 'vite-plugin-imagemin';
export default defineConfig({
plugins: [
viteImagemin({
gifsicle: { optimizationLevel: 3 },
optipng: { optimizationLevel: 7 },
mozjpeg: { quality: 80 },
pngquant: { quality: [0.65, 0.80], speed: 4 },
svgo: {
plugins: [
{ name: 'removeViewBox', active: false },
{ name: 'removeEmptyAttrs', active: true },
],
},
webp: { quality: 80 },
avif: { quality: 50 },
}),
],
});
构建时压缩在部署前处理所有图片,确保图片以最优体积部署到生产环境。质量参数的设置需要权衡:质量过高导致体积偏大,质量过低导致视觉失真。对于大多数场景,JPEG 80%、WebP 80%、AVIF 50% 是合理的起始点。
Sharp — Node.js 图片处理
Sharp 是 Node.js 生态中最强大的图片处理库,它使用 libvips 构建,处理速度极快。
import sharp from 'sharp';
// 基础转换与压缩
async function optimizeImage(inputPath, outputDir) {
const image = sharp(inputPath);
const metadata = await image.metadata();
// 生成 WebP
await image
.webp({ quality: 80, effort: 6 })
.toFile(`${outputDir}/image.webp`);
// 生成 AVIF
await image
.avif({ quality: 50, effort: 6 })
.toFile(`${outputDir}/image.avif`);
// 生成多尺寸
const sizes = [640, 960, 1280, 1920];
for (const width of sizes) {
if (width <= metadata.width) {
await image
.resize(width)
.webp({ quality: 80 })
.toFile(`${outputDir}/image-${width}w.webp`);
}
}
}
// 批量处理
import { glob } from 'glob';
async function processAllImages() {
const files = await glob('src/images/**/*.{jpg,jpeg,png}');
await Promise.all(files.map((file) => optimizeImage(file, 'dist/images')));
}
Sharp 特别适合自动化图片处理流水线。它可以同时处理单张图片和批量处理,支持多种格式转换、尺寸调整、裁剪等操作。effort 参数控制编码时的 CPU 投入,更高的 effort 意味着更好的压缩但更慢的编码速度。
3. 响应式图片
用户使用从 320px 到 2560px 不等宽度的设备访问同一个网站。响应式图片技术确保用户下载适合自己设备尺寸的图片,既不会因为图片过大浪费带宽,也不会因为图片过小而模糊。
srcset 与 sizes
srcset 属性允许你为同一张图片提供多个尺寸版本,浏览器会自动选择最合适的一个:
<!-- 基于宽度的响应式图片 -->
<img
src="image-800w.jpg"
srcset="
image-400w.jpg 400w,
image-800w.jpg 800w,
image-1200w.jpg 1200w,
image-1600w.jpg 1600w
"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw
"
alt="响应式图片示例"
loading="lazy"
decoding="async"
/>
srcset 的语法是 图片URL 宽度描述符,多个版本用逗号分隔。sizes 告诉浏览器这张图片在不同视口宽度下占用多宽。浏览器会根据 sizes 声明和设备像素比(DPR)计算出需要的图片宽度,然后从 srcset 中选择最接近但略大的那个。
picture 元素
<picture> 元素提供了更强大的格式协商能力:
<!-- 格式协商 + 响应式 -->
<picture>
<!-- AVIF 格式(最佳压缩率) -->
<source
type="image/avif"
srcset="image-400w.avif 400w, image-800w.avif 800w, image-1200w.avif 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
/>
<!-- WebP 格式(广泛支持) -->
<source
type="image/webp"
srcset="image-400w.webp 400w, image-800w.webp 800w, image-1200w.webp 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
/>
<!-- JPEG 兜底 -->
<img
src="image-800w.jpg"
srcset="image-400w.jpg 400w, image-800w.jpg 800w, image-1200w.jpg 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
alt="多格式响应式图片"
loading="lazy"
decoding="async"
width="800"
height="600"
/>
</picture>
<!-- 艺术指导(Art Direction) -->
<picture>
<source media="(max-width: 640px)" srcset="hero-mobile.webp" />
<source media="(max-width: 1024px)" srcset="hero-tablet.webp" />
<img src="hero-desktop.webp" alt="Hero image" />
</picture>
浏览器按顺序检查每个 <source> 元素,选择第一个支持的格式。如果浏览器支持 AVIF,就会使用 AVIF 源;否则继续检查 WebP。<img> 是最后的兜底,确保任何浏览器都能显示图片。
**艺术指导(Art Direction)**是 <picture> 的另一个重要用途——为不同屏幕尺寸提供不同裁剪的图片。比如桌面端显示横向全景图,移动端显示纵向裁剪后的重点部分。
4. LCP 图片优化
LCP(最大内容绘制)图片是页面加载性能的关键。优化 LCP 图片可以让页面更快地呈现主要内容。
<!-- LCP 图片专项优化 -->
<head>
<!-- 预加载 LCP 图片 -->
<link
rel="preload"
as="image"
href="hero.webp"
type="image/webp"
fetchpriority="high"
imagesrcset="hero-400w.webp 400w, hero-800w.webp 800w, hero-1200w.webp 1200w"
imagesizes="100vw"
/>
<!-- 预连接图片 CDN -->
<link rel="preconnect" href="https://images.example.com" />
</head>
<body>
<!-- LCP 图片:不使用 lazy loading,设置高优先级 -->
<img
src="hero-800w.webp"
srcset="hero-400w.webp 400w, hero-800w.webp 800w, hero-1200w.webp 1200w"
sizes="100vw"
alt="Hero"
fetchpriority="high"
decoding="async"
width="1200"
height="600"
/>
</body>
LCP 图片优化的关键点:不使用 loading="lazy"——首屏图片必须立即加载;使用 fetchpriority="high"——告诉浏览器这是高优先级资源;预加载关键图片——<link rel="preload"> 让浏览器提前开始下载;预连接 CDN——减少连接建立时间。
5. 图片 CDN 与自动优化
图片 CDN 可以在运行时提供格式转换、尺寸调整等优化,减轻开发团队的负担。
Cloudinary / imgix 集成
// 图片 CDN URL 构建器
class ImageCDN {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
// Cloudinary 风格
getUrl(imagePath, { width, height, quality = 'auto', format = 'auto' }) {
const transforms = [
`w_${width}`,
height ? `h_${height}` : null,
`q_${quality}`,
`f_${format}`,
'c_fill',
'dpr_auto',
].filter(Boolean).join(',');
return `${this.baseUrl}/image/upload/${transforms}/${imagePath}`;
}
// 生成 srcset
getSrcSet(imagePath, widths = [400, 800, 1200, 1600]) {
return widths
.map((w) => `${this.getUrl(imagePath, { width: w })} ${w}w`)
.join(', ');
}
}
const cdn = new ImageCDN('https://res.cloudinary.com/demo');
// React 组件
function OptimizedImage({ src, alt, sizes, widths, priority = false }) {
return (
<img
src={cdn.getUrl(src, { width: 800 })}
srcSet={cdn.getSrcSet(src, widths)}
sizes={sizes}
alt={alt}
loading={priority ? 'eager' : 'lazy'}
fetchPriority={priority ? 'high' : 'auto'}
decoding="async"
/>
);
}
图片 CDN 的工作原理是在 URL 中包含转换参数,CDN 在请求时动态处理。format='auto' 让 CDN 根据请求浏览器的支持情况返回 WebP 或 AVIF;dpr_auto 根据设备像素比返回合适尺寸的图片。
6. SVG 优化
SVG 文件经常包含不必要的元数据、注释和空白,压缩后可以显著减小体积。
// svgo.config.js
module.exports = {
plugins: [
'preset-default',
'removeDimensions',
{
name: 'removeAttrs',
params: { attrs: '(fill|stroke)' },
},
{
name: 'addAttributesToSVGElement',
params: {
attributes: [{ 'aria-hidden': 'true' }],
},
},
],
};
// SVG 作为 React 组件内联使用
// 避免额外的 HTTP 请求,支持 CSS 样式控制
function IconArrow({ className, size = 24 }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
className={className}
aria-hidden="true"
>
<path d="M5 12h14M12 5l7 7-7 7" />
</svg>
);
}
将小图标内联为 React 组件可以减少 HTTP 请求数量,同时允许通过 CSS 控制样式(如颜色、hover 效果)。对于大图标,还是应该作为独立文件加载。
7. 占位图与渐进加载
图片加载过程中的空白或布局跳动会影响用户体验。占位图技术可以改善这种状况。
LQIP(Low Quality Image Placeholder)
// LQIP(Low Quality Image Placeholder)方案
function ProgressiveImage({ src, placeholder, alt, ...props }) {
const [loaded, setLoaded] = useState(false);
return (
<div style={{ position: 'relative', overflow: 'hidden' }}>
{/* 低质量占位图 */}
<img
src={placeholder}
alt=""
aria-hidden="true"
style={{
filter: 'blur(20px)',
transform: 'scale(1.1)',
opacity: loaded ? 0 : 1,
transition: 'opacity 0.3s',
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
{/* 完整图片 */}
<img
src={src}
alt={alt}
onLoad={() => setLoaded(true)}
style={{ opacity: loaded ? 1 : 0, transition: 'opacity 0.3s' }}
{...props}
/>
</div>
);
}
LQIP 先显示一张模糊的低质量占位图,然后过渡到完整图片。这种方案需要先生成一张极低分辨率的缩略图(可以在构建时用 Sharp 生成)。模糊效果使用 filter: blur() 实现,transform: scale(1.1) 是为了在模糊时避免边缘露出白色边框。
实战练习
练习 1:图片格式迁移
将项目中所有 JPEG/PNG 图片转换为 WebP/AVIF 格式,使用 <picture> 元素实现格式协商。使用 Lighthouse 对比迁移前后的性能指标变化,特别关注 LCP 和总阻塞时间。
练习 2:响应式图片系统
搭建一个自动化的图片处理流水线,输入原图后自动生成多尺寸、多格式的响应式图片集。使用 Sharp 或构建插件实现,配置合理的尺寸阶梯(如 400w、800w、1200w、1600w)和质量参数。
练习 3:LCP 图片优化
优化首屏 Hero 图片的加载:对图片进行格式优化、添加预加载标签、配置 fetchpriority 属性。使用 Lighthouse 和 web-vitals 测量优化前后的 LCP 变化,确保 LCP 降低至少 30%。
延展阅读
- web.dev — 图片优化 — Google 官方的图片优化综合指南
- Squoosh — 在线图片压缩 — Google 开发的在线图片压缩工具,支持多种格式和质量对比
- Sharp 文档 — Sharp 官方文档
- 响应式图片指南 — MDN 上的响应式图片完整指南
关键术语
| 术语 | 解释 |
|---|---|
| WebP | Google 开发的现代图片格式,支持有损和无损压缩,比 JPEG 小 25-35% |
| AVIF | 基于 AV1 视频编码的图片格式,压缩率极高,但编码速度较慢 |
| LQIP | Low Quality Image Placeholder,低质量图片占位符技术 |
| srcset | HTML 属性,指定不同分辨率的图片候选 |
| Art Direction | 艺术指导,根据视口尺寸显示不同裁剪/构图图片的技术 |
| fetchpriority | HTML 属性,指定资源加载优先级 |
| CDN | Content Delivery Network,内容分发网络 |
| Sharp | Node.js 高性能图片处理库 |