4、图片压缩:有损压缩(JPEG/WebP/AVIF)、无损压缩(PNG/PNGQuant)、SVG压缩与雪碧图
图片,往往是接口响应体里的「重量级选手」。一张未经处理的截图动辄几 MB,直接拖垮首屏加载。我见过太多团队在接口层面做各种花式缓存,却忽略了图片本身就没优化过——这其实是在用缓存掩盖设计缺陷。
这一节,我们聚焦图片压缩的四种核心手段。说白了,就是让你知道什么场景该用有损、什么场景必须无损、SVG 怎么瘦身、以及雪碧图到底还有没有用。
4.1 有损压缩:JPEG、WebP、AVIF
有损压缩,就是主动丢弃一些人眼不敏感的色彩或细节信息,来换取更小的体积。你想想看,一张照片里相邻的几百个像素颜色几乎一样,没必要每个都精确记录。
JPEG
老牌选手,兼容性最好。几乎所有设备都支持。但 JPEG 有个硬伤——不支持透明通道。而且它在低码率下容易出现「块效应」,就是那种 8x8 格子分明的马赛克感。
我个人的习惯是:照片类图片用 JPEG,质量参数设在 75-85 之间。低于 70 时画质下降明显,高于 90 时体积增长快但收益递减。
WebP
Google 推出的格式,同时支持有损和无损。有损 WebP 比同质量的 JPEG 小 25%-35%。我曾在某个资讯类项目中,把列表页的缩略图全部换成 WebP,接口返回体从 1.2MB 降到了 780KB,首屏速度提升了近 40%。
但要注意:WebP 的解码耗时比 JPEG 高。在低端 Android 设备上,大量 WebP 图片同时解码可能导致 UI 线程卡顿。我的建议是——服务端做格式协商,根据 User-Agent 决定返回 WebP 还是 JPEG。
AVIF
基于 AV1 编码的新格式。压缩率比 WebP 再高 20%-30%,而且支持 HDR 和广色域。但兼容性目前还是短板——iOS 16 之后才原生支持,部分老浏览器完全不认识。
我曾经在内部工具项目里试过 AVIF,效果确实惊艳。一张 2MB 的 PNG 截图,转成 AVIF 后只有 120KB,而且肉眼几乎看不出区别。但生产环境我暂时不敢全面铺开,目前更适合做「渐进增强」——支持 AVIF 的浏览器用 AVIF,不支持的降级到 WebP 或 JPEG。
| 格式 | 压缩率(相对 JPEG) | 透明通道 | 浏览器兼容 | 解码性能 |
|---|---|---|---|---|
| JPEG | 基准 | 不支持 | 100% | 极快 |
| WebP(有损) | 小 25%-35% | 支持 | ~95% | 中等 |
| AVIF | 小 50%-60% | 支持 | ~70% | 较慢 |
4.2 无损压缩:PNG 与 PNGQuant
无损压缩,就是不丢失任何像素信息。PNG 是典型代表。但 PNG 的压缩算法其实分两步:先做「过滤」(预测像素差异),再做「Deflate 压缩」(类似 ZIP)。
很多开发者直接拿 Photoshop 导出 PNG,体积大得离谱。其实 PNG 的压缩率很大程度上取决于你用什么工具。
PNGQuant
这是一个能把 PNG 体积压到极致的工具。它的核心思路是:减少颜色数量。比如一张 32 位 PNG 有 1600 万色,但实际可能只用到了 200 种颜色。PNGQuant 会把颜色量化到 256 色甚至更少,然后重新索引。
我记得有一次,设计给了一张 800KB 的 PNG 图标,我用 PNGQuant 跑了一下:
pngquant --quality=65-80 --speed=1 --output=compressed.png input.png
结果输出只有 120KB,肉眼完全看不出区别。为什么?因为图标本身颜色就少,量化后几乎无损。
另外,PNGQuant 支持生成 8 位 PNG 和 RGBA 半透明。这一点比 GIF 强太多。我见过有人为了透明效果用 GIF,结果体积大、颜色差——其实 PNGQuant 压过的 8 位 PNG 才是正解。
4.3 SVG 压缩
SVG 是矢量图,理论上可以无限缩放。但很多设计师导出的 SVG 文件里,充满了冗余信息:空的 <g> 标签、无用的元数据、编辑器自带的命名空间、小数点后 8 位的坐标值……
我习惯用 SVGO(SVG Optimizer)来做自动化清理。它就像一个代码压缩器,但针对的是 SVG 语法。
npx svgo icon.svg -o icon.min.svg
一个典型的优化效果:
| 优化项 | 原始 | 优化后 |
|---|---|---|
| 文件大小 | 12.5 KB | 3.2 KB |
| 路径精度 | 小数点后 6 位 | 小数点后 2 位 |
| 冗余标签 | 17 个 | 0 个 |
| 元数据 | 包含 | 移除 |
我曾经接手过一个项目,首页用了 40 多个 SVG 图标,总大小 480KB。用 SVGO 批量处理一遍后,降到了 110KB。而且因为是构建阶段自动执行的,后续开发完全无感知。
removeViewBox 插件会删掉 viewBox 属性,导致缩放失效。我的做法是:在 CI 配置里显式禁用不安全的插件,只开启清理类插件。
4.4 雪碧图(CSS Sprites)
雪碧图,就是把多张小图拼成一张大图,通过 CSS 的 background-position 来定位显示哪一块。它的核心价值是减少 HTTP 请求数。
在 HTTP/1.1 时代,每个图片请求都要建立 TCP 连接,浏览器对同一域名还有并发限制(通常是 6 个)。雪碧图能把几十个图标请求合并成一个,效果立竿见影。
但到了 HTTP/2 时代,多路复用让单个连接的并发能力大幅提升。雪碧图的优势被削弱了。我个人现在的判断是:
- 小图标(< 2KB):用雪碧图仍然有价值,因为合并后可以减少请求头开销
- 中等图标(2-10KB):看情况。如果图标数量多且变化少,雪碧图依然有效
- 大图或频繁变动的图:不要用雪碧图。每次改一个图标都要重新生成整张图,缓存失效成本太高
我记得有个电商项目,运营每周都要换促销图标。一开始用了雪碧图,每次更新都要重新拼图、重新部署。后来改成独立 SVG 图标 + HTTP/2 多路复用,反而更灵活,加载速度也没差多少。
<use> 引用)。它既有雪碧图减少请求的优势,又保留了矢量图的清晰度和可维护性。
4.5 如何选择?一张决策表
说了这么多,你可能会问:到底该用哪种?我整理了一张决策表,直接对着选就行:
| 图片类型 | 推荐格式 | 压缩工具 | 备注 |
|---|---|---|---|
| 照片/实拍图 | WebP(有损) | cwebp / libwebp | 兼容性不足时降级 JPEG |
| UI 切图/图标 | PNG(8 位) | PNGQuant | 颜色少时效果极佳 |
| 矢量图标/插画 | SVG | SVGO | 构建阶段自动执行 |
| 文字截图 | PNG(无损) | PNGQuant(高质量) | 不要用有损格式 |
| 大量小图标(HTTP/1.1) | 雪碧图 | 自动化拼图工具 | HTTP/2 下可考虑 SVG Sprite |
| 高压缩率需求 | AVIF | avifenc | 仅用于支持 AVIF 的环境 |
最后说一句:图片压缩不是一次性的工作。我建议在 CI/CD 流程中加入图片压缩步骤,每次构建自动处理。这样开发同学只管上传原图,压缩和格式转换由流水线搞定——既保证质量,又不会有人忘记压缩。