做配图工具链的时候我发现,真正要回答的不是「哪个格式最好」,而是「一张图的哪个环节,该用哪种格式」。
现在的答案很简单:源文件用 SVG,发行文件用 WebP。这篇先把常见格式的区别说清楚,再说这套流程怎么跑。
先分清两个环节#
- 源文件:你要改、要 diff、要重新生成的那一份。它决定「改起来贵不贵」。
- 发行文件:读者浏览器真正下载的那一份。它决定「打开快不快」。
两个目标不一样,最佳格式也就不一样。混在一起讨论,很容易变成「WebP 和 PNG 到底哪个好」这种没有答案的问题。

常见格式差在哪#
| 格式 | 压缩方式 | 透明 | 动画 | 适合 | 不适合 |
|---|---|---|---|---|---|
| PNG | 无损(DEFLATE) | 支持 | 不支持(APNG 另算) | 截图、UI、线条图 | 照片,体积会失控 |
| JPEG | 有损(DCT) | 不支持 | 不支持 | 照片、连续色调 | 文字截图,边缘会振铃、发虚 |
| GIF | 无损,但上限 256 色 | 支持(1 位) | 支持 | 简单表情图 | 渐变、照片,色带明显且体积大 |
| WebP | 有损 + 无损 | 支持 | 支持 | 网页发行 | 需要极老环境兼容时 |
| AVIF | 有损 + 无损 | 支持 | 支持 | 体积敏感的场景 | 回退链更长 |
| SVG | 矢量,无分辨率概念 | 支持 | 支持(CSS / SMIL) | 图标、示意图、图表、封面 | 照片、复杂纹理 |
几个容易混的点:
- JPEG 没有透明通道,需要透明就只能换 PNG 或 WebP;
- GIF 的「无损」不等于画质好,它把颜色压到 256 种,渐变必然出色带;
- SVG 不是位图格式,是图形描述,它的清晰来自矢量,和位图的「清晰」不是一回事。
Google 官方给过一组对比:WebP 无损比 PNG 小 26%,有损在同等 SSIM 质量下比 JPEG 小 25%–34%,无损透明图约是 PNG 的三分之一(来源:developers.google.com/speed/webp)。
拿自己的图跑一遍#
光看结论没感觉,我用手上两张真实图片测了一轮,工具就是项目里的 sharp:
| 格式 | 文字截图 1506×451(原 PNG 114KB) | 彩色示意图 1601×708(原 PNG 472KB) |
|---|---|---|
| WebP 无损 | 77.5KB(68%,像素完全一致) | 161KB(34%,像素完全一致) |
| WebP q90 | 38.0KB(33%) | 34.5KB(7%) |
| JPEG q90 | 45.3KB(40%) | 55.5KB(12%) |
| AVIF q50 | 20.4KB(18%) | 15.7KB(3%) |
| GIF | 43.8KB(38%) | 134KB(28%) |
两个结论:
- 截图和示意图上,WebP 全面压过 PNG 和 JPEG:无损就能省掉三到六成体积,q90 直接省到只剩 7%–33%。GIF 对纯色截图还凑合,一到彩色渐变就是 WebP 的三四倍。
- AVIF 体积更小,但我们没用。同一张示意图上,AVIF q50 编码 77ms、WebP q90 编码 70ms——耗时差不多,体积只有一半。不用的原因不是体积也不是速度,而是:多一种格式就多一条回退链,而我们的图本来只有几十 KB,省下的那 20KB 不值得多维护一层。
为什么源文件用 SVG#
- 它是文本:能 diff、能 review、能参数化。我们每张示意图都是一个模块(
scripts/figures/*.mjs),一帧就是一个状态,改图等于改代码。 - 矢量的好处是确定的:2 倍输出不糊,坐标和字号都是数字,不靠「看着差不多」。
- 颜色跟站点同源:颜色直接从设计 token 里取,不会出现「图里还是上一版的蓝」。
- 还有现实原因:博客需要的配图大多是结构、流程、对比这类图形,恰好是 SVG 的强项。
为什么发行文件用 WebP#
- 体积:上面那张表就是理由。
- 动画:动图用动画 WebP 代替 GIF。GIF 只有 256 色,渐变必有色带;同样的内容在彩色图上,GIF 通常是 WebP 的三四倍。
- 无损也支持透明:想要像素级不变,无损 WebP 就够,不用退回 PNG。
- 支持面:现代浏览器都支持,为兼容性保留 PNG 回退已经没多大必要。
我们是怎么做的#
三类图,三条路径:
| 图类型 | 谁决定 | 用什么 | 怎么产出 |
|---|---|---|---|
| 说明性图(结构、流程、状态) | AI 判断,有必要才加 | 代码生成的 SVG → WebP | pnpm figures <名称> |
| 证据性图(截图、报错、数据) | 个人提供 | 截图 / 照片 → WebP | pnpm images <文件> |
| 封面 | 脚本 | SVG → WebP 960×600 | pnpm generate:covers |
说明性图的流程:
- 在
scripts/figures/<名称>.mjs里写一个模块,按帧参数化生成 SVG(静态图就是一帧); - 跑
pnpm figures <名称>:sharp 渲染成 2 倍图,多帧自动合成动画 WebP(需要.gif时加--format gif); - 生成器会校验尺寸一致、画面非空白、多帧之间确有差异,并打印体积。
证据性图的流程:
pnpm images shot.png --lossless # 无损 WebP,像素完全一致
pnpm images shot.png --quality 90 # 有损,体积更小脚本默认不缩放,只有显式传 --scale / --width 才改尺寸,并且会告警:把截图弄糊的主因是缩小尺寸和反复转码,不是有损压缩。它还会打印 PSNR,方便判断这一档能不能接受。
最后两步:图片上传到 R2(正文图放在 images/posts/<slug>/),文章里用绝对 URL 引用、alt 必填;流程图和时序图优先用 Mermaid,它跟暗色主题联动,改起来也只是改几行文本。
没做的事#
- 不用 AI 画插图:我们的配图要么在「说明」、要么在「证明」,画图模型出来的东西改不动,也难和站点保持同一套配色。
- 不输出 AVIF:观察中,理由上面写了。
- 没有文字越界检测,也没有网页截图工具——示意图的排版目前靠人工核对。
小结#
源文件要能改,发行文件要够小。这两个目标本来就不同,所以格式也不该是同一个。
(实测数据由项目脚本跑出,参数:sharp 0.35.4,WebP effort: 5、JPEG mozjpeg、AVIF effort: 3。)
