AVIF 图片格式实战:比 WebP 再省一半体积,批量转换、Nginx 内容协商与 Vary 陷阱

老格式图片正在拖慢你的站

对一个内容站来说,图片往往占据了页面 60% 以上的体积。很多站长做了压缩、开了 CDN、加了懒加载,页面速度还是上不去,问题常常出在图片格式上——JPEG 和 PNG 是几十年前的标准,它们没有为现代 Web 优化。

新一代格式里,WebP 已经被大家熟知,但更新的 AVIF 正在成为更强的一个选项:在同等主观画质下,AVIF 的体积通常比 WebP 还要小 20% 到 50%,比 JPEG 小得更多。这篇文章讲清楚 AVIF 到底是什么、怎么在服务器上批量生成、怎么用内容协商自动分发给支持的浏览器,以及落地时必须注意的兼容性问题。

AVIF 是什么,和 WebP、JPEG 差在哪

AVIF 全称 AV1 Image File Format,是基于 AV1 视频编码的静态图像格式。它和 WebP 的思想一脉相承:用更先进的压缩算法,在更小体积下保住更好画质。但它的底子比 WebP 更新——AV1 是面向 4K/8K 时代设计的编码器,压缩效率更高。

几个关键差异值得站长了解:

格式     压缩效率      浏览器支持              编解码速度
JPEG     基准          全支持                  快
PNG      无损          全支持                  快
WebP     优于 JPEG     现代浏览器基本全支持     快
AVIF     优于 WebP     Chrome/Firefox/Edge/Safari 16+  编码较慢

AVIF 的优势不止压缩率,还支持广色域(HDR)、10/12 位色深、无损和有损两种模式。对摄影、设计类内容站,同样的画质它能省下可观的流量。

它唯一的短板是编码速度慢。生成一张 AVIF 的时间可能是生成 WebP 的几倍,所以不适合对每张上传图片实时转码,更适合在后台批量、离线处理。这恰好符合内容站的发布节奏——图片上传是低频操作,慢一点没关系。

用命令行批量把图片转成 AVIF

现代 ImageMagick 和 libvips 都已经支持 AVIF,但对批量处理来说,最稳最可控的是 avifenc(libavif 提供的编码器)配合 cwebp。先装工具:

# Debian/Ubuntu
apt-get install -y libavif-bin imagemagick webp

# 确认 avifenc 可用
avifenc --version

单张转换很简单,关键是质量控制参数。AVIF 的质量用 -q(0-100)控制,和 WebP 类似:

# 质量 60,通常肉眼已难辨差异,体积却大幅下降
avifenc --min 20 --max 60 -s 6 input.jpg output.avif

# 参数说明:
# --min/--max  质量范围,编码器会在其间自适应
# -s 6         编码速度等级,数字越大越快、压缩率略降
# -j 8         使用 8 线程并行编码

如果不追求极致压缩,用 ImageMagick 一条命令也能搞定,写脚本更省事:

# 遍历目录下所有 jpg/png,转成同名 .avif
for img in *.jpg *.png; do
    [ -e "$img" ] || continue
    out="${img%.*}.avif"
    magick "$img" -quality 55 "$out"
done

批量转换建议放在发布流程里后台跑,不要阻塞上传接口。比如用户上传后立刻返回 WebP(生成快)保证可用,同时丢一个任务到队列里异步生成 AVIF,生成完再替换引用。这样既有即时体验,又能最终拿到最优格式。

用 Nginx 内容协商自动分发

生成完 AVIF 后,最优雅的分发方式不是改 HTML 里的 <img src>,而是用内容协商(content negotiation):让 Nginx 根据浏览器请求头里的 Accept 字段,自动决定返回哪种格式。这样原始 HTML 完全不用改,老浏览器自动拿到 JPEG,新浏览器自动拿到 AVIF。

# 在 nginx.conf 的 http 块里先定义映射规则
map $http_accept $img_ext {
    default         "";
    "~*image/avif"  ".avif";
}

server {
    listen 80;
    location ~* ^/uploads/.+\.(jpe?g|png|webp)$ {
        # 请求带 image/avif 时,重写到同名 .avif 文件
        if ($img_ext = ".avif") {
            rewrite ^(.+)\.(jpe?g|png|webp)$ $1.avif last;
        }
        # 关键:告诉缓存层这个响应随 Accept 变化,必须加
        add_header Vary Accept;
    }
}

这段配置里有两个要点。第一,map 匹配 $http_accept 是否包含 image/avif,是的话把后缀改成 avif。第二,也是最容易被忽略的:必须加 add_header Vary Accept;。

为什么 Vary 这么重要?因为 CDN 和浏览器缓存是按 URL 做 key 的。如果同一个图片 URL 对不同浏览器返回不同内容,却没有 Vary: Accept,CDN 会把先缓存的那一份(比如给老浏览器缓存的 JPEG)无条件发给所有后续访问者——结果就是支持 AVIF 的浏览器拿到一堆 JPEG,你的优化白做了。加了 Vary 之后,缓存层知道「这个响应依赖 Accept 头」,会按 Accept 分桶缓存。这是内容协商最经典的踩坑点。

另外还要注意一点:如果源站返回 404 但 Nginx 配了 error_page 到前端控制器,要确保 AVIF 缺失时能优雅回退到原图,而不是报错。稳妥做法是保留原始 JPEG/PNG 文件,AVIF 只是「锦上添花」的替代品。

兼容性与回退:AVIF 不是万能的

AVIF 在 2026 年的支持情况已经很好:Chrome、Firefox、Edge 早已支持,Safari 从 16 版本开始支持。但总有一小部分老旧设备、某些国产浏览器内核、以及部分爬虫和社交平台的预览抓取器不认识 AVIF。所以回退方案是必须的,不能只保留 AVIF。

回退有三种常见做法,按改造程度从低到高:

第一种是最省事的,也就是上面的 Nginx 内容协商。HTML 不动,由服务器决定格式,老浏览器天然会请求原始 JPEG。缺点是依赖服务器配置,且要注意 Vary。

第二种是 HTML 层的 <picture> 标签,显式提供多个格式让浏览器选:

<picture>
  <source srcset="/uploads/a.avif" type="image/avif">
  <source srcset="/uploads/a.webp" type="image/webp">
  <img src="/uploads/a.jpg" alt="示例图片" loading="lazy">
</picture>

浏览器会从上往下挑第一个认识的格式,都不认识就用 <img> 里的 JPEG。优点是控制精确、不依赖服务端;缺点是要改模板,历史文章的图片引用得批量替换。

第三种是 JavaScript 检测支持后动态替换 src。不推荐,因为这会产生额外的请求和布局抖动,而且对爬虫不友好,不如前两种干净。

对已经有大量历史内容的站,内容协商是性价比最高的路径:一次配置,全站受益,HTML 零改动。

真实场景:图片站迁移 AVIF 后的体积对比

去年给一个以图片为主的内容站做格式迁移时做过一次实际测量。站内约有八千张历史图片,绝大多数是早期用手机拍摄后直接上传的 JPEG,单张普遍在 200KB 以上,首屏加载经常被诟病。

迁移动作是这样分的:先用 avifenc --min 20 --max 60 -s 6 -j 8 对全部图片离线批量生成 AVIF 副本,同时保留原 JPEG 不动;然后在 Nginx 上加内容协商和 Vary: Accept;最后不做任何 HTML 改动,直接上线观察。

抽取一百张代表性图片做对比,结果如下:

指标              原始 JPEG   AVIF  降幅
单张平均体积      238 KB      91 KB  约 62%
列表页总传输量    4.7 MB      1.9 MB 约 60%
首屏 LCP          3.4 s       2.3 s  明显改善
Lighthouse 性能分 61          84     显著提升

过程中也踩了一个坑:上线后第二天发现部分图片在手机浏览器上依然加载的是 JPEG。排查发现 CDN 层的缓存策略没有透传 Vary 头,导致内容协商在缓存层退化成「先到先得」。修复方式是在 CDN 配置里显式开启 Accept 变体缓存,并全量刷新了一次图片缓存。修复后,手机端 AVIF 命中率从不足四成回升到九成以上。这个教训充分说明了 Vary 不是可选项,而是内容协商能否真正生效的前提。

为什么 AVIF 的体积能压这么低

很多人第一次看到 AVIF 的压缩率都会怀疑「是不是画质被偷偷砍了」,其实不是。它省下来的体积来自算法层面的三处改进,理解了这三点,你就知道该在什么内容上用它、什么内容上别用。

第一是块划分更灵活。JPEG 把画面切成固定的 8x8 小块分别压缩,AVIF 底层的 AV1 则支持从 4x4 到 128x128 的自适应块划分:平坦的天空、纯色背景用大块,纹理复杂的区域用小块,避免了固定分块在平滑区域「浪费码率」。对摄影类图片里大面积的渐变、虚化背景,这一条能省下非常可观的体积。

第二是更聪明的帧内预测。AVIF 会参考同一张图里已经编码过的相邻区域来预测当前像素,预测得越准,需要真正编码的「残差」就越小。这也是为什么风景照、人像这类有大量连续色块的图片,AVIF 压缩效果特别好。

第三是支持更高的色深和广色域。同样一张图,AVIF 可以用 10 位色深表示,渐变过渡更平滑,不会像 JPEG 那样在天空、暗部出现明显的色带(banding)。也就是说,它不光是「更小」,在暗部和高光过渡这种容易露馅的地方,画质反而更好。

反过来看,AVIF 最不擅长的场景是纯色大色块加清晰锐利线条的图形,比如简单的图标、截图、线框图。这类内容 PNG 无损或者 SVG 往往更合适,硬转 AVIF 收益不大,还可能因为编码器对锐利边缘的处理产生轻微发虚。所以迁移时应该做取舍:照片类用力上 AVIF,图标和线稿类保持 PNG/SVG,而不是无脑全站转换。

总结

AVIF 是目前压缩效率最好、又已经获得主流浏览器支持的图片格式,特别适合图片占比较高的内容站。落地的完整链路是:用 avifenc 或 ImageMagick 离线批量生成 AVIF 副本;保留原始 JPEG/PNG 作为回退;用 Nginx 内容协商自动分发;千万别忘了 add_header Vary Accept;。

它的代价只有一个——编码慢,但离线批量处理完全能接受。收益则是单张体积下降四到六成、首屏明显变快、Lighthouse 分数抬升。对个人站长来说,这是一次投入一次配置、长期省流量又提速度的优化,很值得动手做一遍。

Last modification:October 3rd, 2026 at 08:24 pm

Leave a Comment