JPEG、WebP、AVIF 到底该选哪个

三张图,三种结论

先说这篇文章存在的理由:「同等画质下 AVIF 比 JPEG 小一半」这句话被引用得太多,以至于它听起来像一个常数。它不是。下面这三张样图都缩到 1200 像素宽,都在同一台机器上用同一套方法压到相同的 SSIM,AVIF 相对 JPEG 省下的比例是:照片 49.76%、截图 81.24%、插画 92.88%——插画省掉的比例差不多是照片的 1.9 倍。省多少完全取决于图里画的是什么。

测试环境:Apple M1 Pro(6 性能核 + 2 能效核)、macOS 26.5.2,编码器是 cjpeg(libjpeg-turbo 3.1.0)、cwebp 1.5.0、avifenc 1.3.0(内置 aom 3.12.1),测试日期 2026 年 9 月 14 日。下面三张主表的耗时一律是单线程、同一轮里交错执行、每条命令跑 15 次取最小值;后文另有两张表来自独立的测量轮次,各自在表边上注明。

样图一:照片,1200×675,macOS 自带壁纸 DefaultAerial.jpg(湖面、碎石、远山、大片渐变天空)缩放而来,细节密集。

格式 编码参数 体积 相对 JPEG RGB-SSIM 编码耗时
JPEG cjpeg -quality 80 114,293 B — 0.941648 6.93 ms
WebP cwebp -q 78 79,784 B −30.19% 0.942556 85.52 ms
AVIF avifenc --speed 6 -q 52 57,419 B −49.76% 0.942596 566.84 ms

样图二:插画,1200×750,用 ImageMagick 画的扁平风景——纯色天空、几块多边形山体、一个圆太阳,全部是大面积平涂加硬边。

格式 编码参数 体积 相对 JPEG RGB-SSIM 编码耗时
JPEG cjpeg -quality 80 26,149 B — 0.989271 8.42 ms
WebP cwebp -q 37 4,746 B −81.85% 0.989283 52.68 ms
AVIF avifenc --speed 6 -q 24 1,863 B −92.88% 0.990518 120.93 ms

样图三:代码截图,1200×968,把本仓库 src/lib/image.ts 的 38 行源码用 Menlo 渲染成编辑器窗口的样子,浅色底、深色等宽文字、彩色行号与关键字。

格式 编码参数 体积 相对 JPEG RGB-SSIM 编码耗时
JPEG cjpeg -quality 80 86,269 B — 0.984073 10.09 ms
WebP cwebp -q 23 25,078 B −70.93% 0.984146 80.09 ms
AVIF avifenc --speed 6 -q 30 16,181 B −81.24% 0.986189 239.98 ms

照片那一张是三张里 AVIF 优势最小的——恰恰是大多数「AVIF 省一半」的论断取样的地方。真正夸张的是插画:26,149 B 变成 1,863 B,倍数是 14.0 倍,而同一张图 WebP 只做到 5.5 倍。成因我没有单独做实验验证,通常的解释是:大面积平涂对 JPEG 的 8×8 分块 DCT 是最不利的一类内容,硬边两侧会持续产生振铃,而 AV1 的帧内预测可以用一个大块整片地覆盖掉纯色区域,几乎不花码率。

主表里有一处口径不对等,得单独交代,因为它对 AVIF 是不利的。三个编码器除 quality 外都用各自的默认值,而这些默认值并不在同一档上:cjpeg 默认 2x2,1x1,1x1(也就是 4:2:0)、8 bit,有损 WebP 在格式层面就只有 4:2:0、8 bit,而 avifenc 1.3.0 从 PNG 输入编码时默认走的是 YUV444 + 12 bit——它每次都会把这两行打在自己的输出里,很容易被当成噪声划过去。换句话说,主表里的 AVIF 是在更严格的色度与位深规格下测出来的。

我按同一套 SSIM 目标把 --yuv 420 --depth 8 的 AVIF 重新对齐了一遍,结果和「降规格就该更小」的直觉相反:照片从 57,419 B 变成 61,046 B(大 6.32%),插画从 1,863 B 变成 2,021 B(大 8.48%),截图从 16,181 B 变成 16,092 B(小 0.55%)——三张里有两张反而变大了。 这个比较对 444 略有利,得说明白:SSIM 我是在 RGB 三通道上算的,保住色度本身就会加分。真正被默认值抬高的是耗时——在同一轮交错测量里,420/8 bit 比默认档快 17.5%(照片)、28.6%(插画)、22.5%(截图)。所以主表对 AVIF 偏严的地方只在耗时那一列,体积那一列它并没有吃亏。对你的实际意义是:跑 avifenc 时只要不显式写上 --yuv 420 --depth 8,拿到的就是主表这一档——多付的这部分时间换来的是更完整的色度,大多数场合不必去动它。

「视觉质量相当」是怎么定的,以及我没有做什么

这是全文最容易出问题的地方,所以说清楚:我没有做人眼盲测。 上面三张表里的「相当」是一个客观指标对齐的结果,具体做法是——先用 cjpeg -quality 80 编出 JPEG 作为基准,测出它相对原始 PNG 的 SSIM;再对 WebP 和 AVIF 做二分搜索,找出 SSIM 不低于这个基准值的最小 quality 值,也就是在不比 JPEG 差的前提下体积最小的那一档。

SSIM 用 scikit-image 0.21.0 的 structural_similarity 计算,参数是 data_range=255、channel_axis=2、gaussian_weights=True、sigma=1.5、use_sample_covariance=False,即 Wang 等人原论文那套 11×11 高斯窗设置。特意在 RGB 三通道上算而不是只算亮度,是因为三个编码器对色度的处理方式不同,只看亮度会让色度上的损失完全不进账。AVIF 的输出用 avifdec 解回 PNG 再参与比较。

这套对齐有两个必须说出来的局限。一是搜索是整数步长的,而 avifenc 的 quality 到量化参数不是一一映射:照片那张图 -q 52 和 -q 53 产出的是同一个字节数(57,419 B),截图那张 -q 28 和 -q 29 也一样,所以「最小可行 q」这个说法在个别位置有一到两档的粒度误差。二是 SSIM 相等不等于观感相等。 我把截图那张的三种输出裁出同一块 560×120 的区域放大到 200% 逐个看过:JPEG 在文字边缘有典型的振铃杂边,WebP 是整体发虚,AVIF 则在字符周围出现了很淡的彩色晕染。三种劣化的形状完全不同,而 SSIM 给它们打了几乎一样的分。如果你的图对某一类劣化特别敏感(比如品牌色的 logo 怕色偏),这个对齐方式就不够用,得自己看。

AVIF 的账单在 CPU 上

把上面三张表的耗时列单独拎出来看:同样是把一张 1200 像素宽的图编一遍,AVIF 花的时间是 JPEG 的 81.8 倍(照片)、14.4 倍(插画)、23.8 倍(截图)。这个差距还被低估了——同样一条 cjpeg 命令换成一张 1×1 的输入,最小耗时是 3.83 ms,也就是说照片那 6.93 ms 里有一大半是进程启动,真正花在 JPEG 编码上的只有 3 ms 出头;而 AVIF 那 566.84 ms 里启动开销(6.17 ms)可以忽略不计。还有一处口径要交代:cjpeg 读的是 PPM,cwebp 和 avifenc 读的是 PNG,后者多出一次 PNG 解码——我用同一张图分别喂 PNG 和 PPM 给 cwebp 量过,这一项是 2.86 到 10.19 ms。把启动开销和 PNG 解码都扣掉,照片那一行仍然是 3.10 ms 对 550.48 ms,差距只会比表里的倍数更大,不会更小。

--speed 这个参数值得单独拿出来说,因为它是 AVIF 唯一真正能调的旋钮。下面是照片那张图固定 -q 52、单线程、从最快到最慢跑一遍的结果。这是另一轮独立测量,同一轮里 speed 6 测到 578.0 ms,比上表的 566.84 ms 高 2.0%——这个量级的波动在跑批测试里属于正常,所以跨表比较耗时的时候别抠最后一位:

--speed 编码耗时 体积 RGB-SSIM
10 61.6 ms 58,081 B 0.930344
9 61.7 ms 58,081 B 0.930344
8 221.0 ms 163,322 B 0.966954
7 285.4 ms 53,992 B 0.935754
6 578.0 ms 57,419 B 0.942596
5 2400.1 ms 56,538 B 0.944933
4 3211.2 ms 56,323 B 0.944839
3 6280.8 ms 55,685 B 0.945355
2 10695.1 ms 56,958 B 0.947741
1 12905.4 ms 57,074 B 0.947913
0 27817.3 ms 57,010 B 0.948015

这张表里最该记住的是首尾两行的对比:从 speed 6 降到 speed 0,耗时涨了 48.1 倍(578.0 ms → 27817.3 ms),换来的是体积小 0.71%、SSIM 高 0.0054。 一张 1200 像素宽的图在最慢档要编近 28 秒,而省下的 409 个字节在任何真实场景里都不值得。speed 6 是 avifenc 1.3.0 的默认值,也是我认为绝大多数人该停在的地方。

表里还有两处反常,如实记下来:speed 9 和 speed 10 的输出字节完全相同,看起来这两档在 aom 内部被折叠成了同一个配置;而 speed 8 那一行产出了 163,322 B——是 speed 6 的 2.84 倍,SSIM 也高出一截,说明它走的是另一套码率控制路径,同一个 -q 52 在这一档上根本不是同一个目标质量。这两个现象我在两次独立运行里都复现了,但我没有去读 aom 的源码求证成因,所以只报告观察,不给解释。别把 avifenc 的 -q 当成跨 speed 档可比的刻度。

这条耗时曲线决定了 AVIF 该放在哪

选型时最容易被忽略的不是体积也不是兼容性,而是这条曲线落在你的哪条链路上。

预先批量处理的静态资源,AVIF 几乎无脑该上。 构建期多花的时间是一次性的,之后每一个访客都在收益。按上面的数字算,单线程处理 1000 张这样的照片,AVIF 要 9.4 分钟,JPEG 只要 7 秒——这 9 分多钟对一条 CI 流水线来说是可以接受的代价,何况这件事可以并行、可以缓存、可以只在图片真的变了的时候重跑。

用户上传后实时转换,AVIF 基本不该上。 把 --jobs all 打开,照片那张图从 566.84 ms 降到 151.80 ms(8 个核只换来 3.73 倍加速,并行效率并不高),听起来还行——但这 152 ms 是独占整机 8 个核换来的。真正该比的是单核占用时间:一张图 JPEG 花 6.93 ms,AVIF 花 566.84 ms,同一台机器的吞吐相差 81.8 倍。同时有十几个用户传图,你要么排队,要么每个人都等上秒级。合理的做法是上传时先出一份 JPEG 或 WebP 立刻可用,AVIF 丢给离线队列慢慢补。

中间格式、临时预览、需要秒回的场景,直接用 JPEG。 在这条链路上省下的那几十 KB,换不回用户多等的那几百毫秒。

浏览器支持:只给数字不给日期的都别信

这类数字过期最快,所以先说来源和方法:下面的数据来自本机 node_modules 里的 caniuse-lite 1.0.30001810,我在 2026 年 9 月 14 日用一段脚本遍历它的 stats 与各浏览器的 usage_global,把标记为完全支持(y)的版本用量加总得出。caniuse 的统计口径本身不到 100%——它跟踪到的浏览器总用量是 96.688%,剩下的是没有用量数据的长尾。

各家第一个被标为完全支持的版本,AVIF 是 Chrome 85、Firefox 93、Safari 16.4(16.1–16.3 标为部分支持)、iOS Safari 16.0(条目上带附注)、Edge 121、Samsung Internet 14.0;WebP 是 Chrome 32、Firefox 65、Safari 16.0(14–15 标为部分支持)、iOS Safari 14.0、Edge 18、Samsung Internet 4。

这里有一个很多人会猜错的点:Edge 直到 121 才支持 AVIF,尽管它从 79 起就是 Chromium 内核——这个特性在 Edge 里被单独关了很久。所以「Chromium 内核都支持」这种推断在 AVIF 上是不成立的,你只能查表。

还要提醒一句:这两个百分比是全球流量口径,而你面对的从来不是全球流量。企业内网、教育网、政府采购的设备、东南亚的低端安卓机,它们的浏览器版本分布和这张表可以差得很远。真正该看的是你自己的访问日志。

透明通道与动图:JPEG 两样都没有

这不是「支持得好不好」的程度问题,是有和没有的问题。我做了一次往返验证:拿一张 400×400、只在中心画了一个实心圆、其余全透明的 PNG,分别编码再解回来,检查左上角 (2,2) 那个像素。

cwebp -quiet -q 80 alpha.png -o alpha.webp      # 解回来 (0, 0, 0, 0),透明保住了
avifenc --speed 6 -q 60 alpha.png alpha.avif    # 解回来 (0, 0, 0, 0),透明保住了

magick alpha.png -background white -flatten -strip alpha-flat.ppm
cjpeg -quality 80 -outfile alpha.jpg alpha-flat.ppm  # 解回来 (255, 255, 255, 255)

JPEG 那条命令前面多出来的 -flatten 不是我为了省事加的:JPEG 根本没有 alpha 通道,透明区域必须先被合成到某个背景色上,这一步不是可选项。把带透明的图转成 JPEG,你不是在压缩它,你是在替它做一个不可逆的背景色决定。

动图同理。我用 4 帧 200×200 的序列试了一遍:img2webp 产出 1,844 字节的 4 帧动态 WebP,avifenc --fps 10 f1.png f2.png f3.png f4.png anim.avif 产出 4,992 字节的 4 帧 AVIF 序列(avifdec --info 能读出 4 frames、Repeat Count : Infinite)。JPEG 这边没有对应的东西可试。

渐进式加载:JPEG 在这件事上反而领先

渐进式 JPEG 把图像按频率分成多遍扫描,弱网下先显示一个模糊但完整的全图,再逐遍变清晰;基线 JPEG 只能从上往下一行行刷出来。观感上的优势我没有办法在本机量化——它取决于网络条件和人的主观感受,我不会假装测过。但有一件事可以量化,而且结果和大多数人的预期相反:

样图 基线 JPEG 渐进式 JPEG 体积变化 耗时变化
照片 114,293 B 108,835 B −4.78% 2.51×
插画 26,149 B 18,349 B −29.83% 1.59×
截图 86,269 B 71,356 B −17.29% 1.86×

渐进式 JPEG 不但没变大,三张图全都变小了,插画那张小了近三成。代价只是编码时间涨到 1.6 到 2.5 倍——而 JPEG 的编码本来就只有几毫秒。cjpeg -progressive 基本是白捡的,唯一要注意的是解码端内存占用略高,以及极老的软件可能不认。

另外两种格式在这件事上都拿不出对等的东西。WebP 没有渐进式模式:cwebp 1.5.0 的选项列表里没有任何渐进式或隔行相关的开关。AVIF 有,但代价大到不实用:avifenc 1.3.0 的 --progressive 在帮助文本里明确标着 EXPERIMENTAL,我实测照片那张图开启后从 57,419 B 涨到 114,889 B——正好是 2.00 倍,耗时从 569.2 ms 涨到 1763.1 ms(这一对是在同一轮里测的,那轮的普通编码是 569.2 ms 而不是首表的 566.84 ms,仍然是运行间波动)。它的原理是把图编成多个分层,体积翻倍是这个结构的直接后果。至于各家浏览器是否真的会分层渐进渲染它,我在本机没有办法验证,所以不下结论。

什么时候 JPEG 仍然是对的选择

抛开体积,JPEG 有三个别的格式短期内替不掉的位置。

一是需要最大兼容性的时候。 不只是浏览器——邮件客户端、打印工作流、老的 CMS、各种设备固件里的图片查看器、第三方 SDK 的头像上传接口。这些地方的更新周期以年计,caniuse 上那 94.647% 完全反映不了它们。凡是图片要离开你的网页、进入别人系统的场合,JPEG 是唯一不需要先问一句「你们支持吗」的格式。

二是需要极快编码的时候。 前面已经算过了:扣掉进程启动与 PNG 解码之后,照片那张图 JPEG 花 3.10 ms、AVIF 花 550.48 ms,这是两种不同性质的东西。缩略图、实时预览、用户传完图就要立刻看到结果的交互,这个差距直接决定体验。

三是图片会被第三方系统再加工的时候。 下游可能要裁剪、加水印、重新缩放、二次压缩。有损格式的损失会累积,而累积这件事在所有格式上都成立——但 JPEG 至少有 jpegtran 这类无损变换工具,能在不解码重编的前提下做旋转和裁剪。WebP 和 AVIF 在这条工具链上要单薄得多。

顺带一个意外收获,和「截图该用什么」直接相关。截图那张样图我顺手试了一次 cwebp -lossless:21,058 字节,SSIM 等于 1.000000,完全无损。 它比同一张图对齐后的有损 WebP(25,078 B)还小 16.03%,比 JPEG 的 86,269 B 小 75.59%。对于截图、UI 图、纯色插画这类内容,先试无损 WebP 是一个很容易被跳过但经常直接胜出的选项——顺便还绕开了「有损压缩把文字边缘搞糊」这个老问题。本站的图片压缩工具在浏览器里做同类的事,可以先拿它快速比一比再决定用什么跑批。

<picture> 的回退写法

浏览器会按顺序取第一个它认识的 type,所以排列顺序必须是体积从小到大——把 AVIF 放最前,JPEG 放最后兜底:

<picture>
  <source srcset="/img/hero.avif" type="image/avif">
  <source srcset="/img/hero.webp" type="image/webp">
  <img src="/img/hero.jpg" alt="日落时分的湖岸" width="1200" height="675"
       loading="lazy" decoding="async">
</picture>

有几处细节不能省。alt、width、height 只能写在 <img> 上,写到 <source> 上不生效——漏掉宽高会导致图片加载完成时页面跳动。loading="lazy" 不要加在首屏图片上,那会推迟你最该早点开始的那次加载。<source> 上没有 src 属性,只有 srcset。

配合 srcset 做多分辨率时,每个 <source> 各自带自己那一套:

<picture>
  <source type="image/avif"
          srcset="/img/hero-800.avif 800w, /img/hero-1600.avif 1600w"
          sizes="(max-width: 900px) 100vw, 900px">
  <source type="image/webp"
          srcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
          sizes="(max-width: 900px) 100vw, 900px">
  <img src="/img/hero-800.jpg" alt="日落时分的湖岸" width="800" height="450">
</picture>

最后一个容易被忽略的点:<picture> 只解决 HTML 里的图。CSS 背景图、og:image、邮件模板、<link rel="preload"> 里的图都不走这条路径。og:image 尤其要小心——抓取它的是各家社交平台和聊天软件的服务端,不是浏览器,它们对 AVIF 的支持和 caniuse 上那个数字没有任何关系,这个位置放 JPEG 最省心。如果你的图是服务端动态出的,也可以改成看请求头里的 Accept 再决定回哪种格式,但那样必须记得带上 Vary: Accept,否则 CDN 会把 AVIF 缓存下来喂给不认识它的客户端。

图片压缩工具在线压缩 JPEG、PNG、WebP 图片,支持批量处理、质量调节与尺寸缩放,全部在浏览器内完成,图片不上传。打开工具 →