WebP AVIF 区别怎么选:picture 标签与 CDN 自动转换
WebP 和 AVIF 的区别,简单说是:AVIF 在同等观感下通常更小,尤其是照片,但编码慢、个别场景下文字边缘会糊;WebP 体积比 JPEG/PNG 小,编码快,兼容面更宽,对截图和带透明的图更稳。所以实际做法通常是两种都出,用 <picture> 让浏览器自己挑,再用 JPEG/PNG 兜底。
格式选对只解决了一半。另一半是尺寸和加载顺序:给手机发一张 2400px 宽的 AVIF,照样是浪费。下面按图片类型给建议,再给可以直接复制的 HTML 模板、批量转换脚本,最后讲 CDN 自动转格式时容易踩的缓存坑。
WebP 和 AVIF 到底差在哪?
两者都是为了替代 JPEG 和 PNG 设计的格式,都支持有损、无损和透明通道。差别主要在下面几处:
| 对比项 | WebP | AVIF |
|---|---|---|
| 编码基础 | VP8(有损)/ 专门的无损编码 | AV1 视频编码的帧内压缩 |
| 有损照片体积 | 通常比 JPEG 小 | 通常比 WebP 再小一些,差距因图而异 |
| 编码速度 | 快,适合构建时大量处理 | 慢得多,大图批量转换要预留时间 |
| 文字、细线 | 无损模式效果好 | 低质量参数下文字边缘容易发糊,色度抽样要注意 |
| 高位深 / HDR | 只支持 8 位 | 支持 10/12 位 |
| 动图 | 支持 | 支持,但工具链和兼容性不如 WebP 成熟 |
| 浏览器支持 | 主流浏览器都已支持 | 主流浏览器的新版本都已支持,旧版 Safari 和旧系统是主要缺口 |
关于兼容性,网上很多对比文写于 2021 年前后,那时 AVIF 还只有 Chrome 支持,结论已经过时。现在 Chrome、Firefox、Edge 和 Safari 的较新版本都能解码 AVIF,缺口主要在不再更新的旧 iOS / macOS 设备和一些内嵌 WebView。具体版本号以 MDN 和 caniuse 的最新数据为准,不要照抄老文章。对国内站点来说,一些 App 内置浏览器的内核版本可能偏旧,这也是保留 WebP 和 JPEG 兜底的主要理由。
“AVIF 比 WebP 小百分之多少”这类数字,各家测试集不同,结论差得很远。本文不给统一的比例,你应该用自己的图测一遍,方法在后文。
不同类型的图片该选哪种格式?
按内容分类比按“哪个格式更先进”选更靠谱:
| 图片类型 | 首选 | 备选 / 兜底 | 说明 |
|---|---|---|---|
| 照片、Banner、商品图 | AVIF 有损 | WebP 有损 → JPEG | AVIF 收益最明显的场景 |
| 界面截图、带大量文字的图 | WebP 无损或高质量有损 | PNG | AVIF 要用 4:4:4 色度抽样并提高质量,否则字边发虚 |
| 插画、扁平色块图 | 两种都试 | PNG | 色块大时无损 WebP 往往已经很小 |
| 带透明的图标、Logo | SVG | WebP 无损 → PNG | 能矢量化就别用位图 |
| 动图 | 视频(MP4/WebM) | 动画 WebP → GIF | 长动图直接换 <video> 更省 |
判断标准只有一个:同一张图、同一观感下,哪个文件更小。用肉眼在 100% 和 200% 缩放下对比,重点看文字边缘、渐变天空和人脸肤色这三处最容易出问题的地方。
picture、srcset、sizes 怎么写?
下面这个模板覆盖了大多数内容图片。复制后改路径和宽度即可:
<picture>
<source
type="image/avif"
srcset="/img/hero-640.avif 640w, /img/hero-1024.avif 1024w, /img/hero-1600.avif 1600w"
sizes="(max-width: 768px) 100vw, 768px">
<source
type="image/webp"
srcset="/img/hero-640.webp 640w, /img/hero-1024.webp 1024w, /img/hero-1600.webp 1600w"
sizes="(max-width: 768px) 100vw, 768px">
<img
src="/img/hero-1024.jpg"
srcset="/img/hero-640.jpg 640w, /img/hero-1024.jpg 1024w, /img/hero-1600.jpg 1600w"
sizes="(max-width: 768px) 100vw, 768px"
width="1600" height="900"
alt="页面主图的文字描述"
decoding="async">
</picture>
几个容易写错的地方:
<source>的顺序就是优先级。浏览器从上往下找第一个支持的type,所以 AVIF 必须写在 WebP 前面。sizes要和 CSS 实际宽度一致。上面的意思是:视口不超过 768px 时图片占满屏宽,否则固定 768px。浏览器拿这个值乘以设备像素比,再从srcset里挑最接近的一档。sizes写错,浏览器就会下载过大或过小的图。width和height必须写在<img>上。写原图的宽高即可,浏览器据此算出宽高比,提前留位置,避免图片加载完把正文往下顶(CLS 布局偏移)。CSS 里再加img { max-width: 100%; height: auto; }。- 属性都放在
<img>上。alt、loading、fetchpriority、width/height写在<img>上才生效,<source>只负责提供候选文件。
loading="lazy" 和 fetchpriority 该放在哪?
- 首屏最大的那张图(通常也是 LCP 元素):不要加
loading="lazy",加fetchpriority="high",让浏览器尽早请求。 - 首屏以下的图:加
loading="lazy",滚动到附近才加载。 - 轮播图里不可见的几张:
loading="lazy",或干脆等交互后再插入。
最常见的错误,是框架或 CMS 给所有图片统一加了 loading="lazy",首屏主图也被延后,LCP 反而变差。检查方法:打开 DevTools 的 Network 面板,按 Priority 列排序,确认首屏主图的优先级是 High。
尺寸档位怎么定?
不用每 100px 一档。按页面里图片实际显示的最大 CSS 宽度,乘以 1 倍和 2 倍,再加一个手机档位就够了。例如正文图最宽显示 768px,档位可以定为 640、1024、1600。档位越多,构建产物和 CDN 缓存的对象越多,收益却很有限。
怎么用命令行批量转换?
以前常推荐 Squoosh CLI,但它的命令行包已经不再维护,新项目建议直接用 sharp(Node.js,底层是 libvips)或官方编码器 cwebp / avifenc。
用 sharp 批量生成多尺寸多格式
npm i -D sharp
把下面的脚本存为 scripts/images.mjs:
import sharp from "sharp";
import { readdir, mkdir } from "node:fs/promises";
import path from "node:path";
const SRC = "assets/images";
const OUT = "public/img";
const WIDTHS = [640, 1024, 1600];
await mkdir(OUT, { recursive: true });
const files = (await readdir(SRC)).filter(f => /\.(jpe?g|png)$/i.test(f));
for (const file of files) {
const name = path.parse(file).name;
const input = sharp(path.join(SRC, file));
const { width: origW } = await input.metadata();
for (const w of WIDTHS) {
if (w > origW) continue; // 不放大原图
const base = input.clone().resize({ width: w });
await base.clone().avif({ quality: 50, effort: 4 }).toFile(`${OUT}/${name}-${w}.avif`);
await base.clone().webp({ quality: 75 }).toFile(`${OUT}/${name}-${w}.webp`);
await base.clone().jpeg({ quality: 78, mozjpeg: true }).toFile(`${OUT}/${name}-${w}.jpg`);
}
console.log("done", file);
}
node scripts/images.mjs
质量参数是起点,不是标准答案。截图类图片可以改成 .webp({ lossless: true }),或者给 AVIF 加 chromaSubsampling: "4:4:4" 并调高 quality。effort 越高压得越小,也越慢,CI 里时间紧可以调低。
用 cwebp / avifenc 处理单张
cwebp -q 75 photo.jpg -o photo.webp
cwebp -lossless screenshot.png -o screenshot.webp
avifenc -q 60 -s 6 photo.jpg photo.avif
不同版本的 avifenc 参数略有差异,以 avifenc --help 的输出为准。
怎么验证转换后真的变小了?
ls -l public/img | sort -k5 -n | awk '{print $5, $9}'
把同一档位的 .avif、.webp、.jpg 放在一起比较。如果某张图的 AVIF 反而比 WebP 大(小图、纯色图偶尔会这样),可以在 <picture> 里去掉这张图的 AVIF 来源,或者在脚本里加一条“比 WebP 大就删掉”的判断。
CDN 自动转换格式时,缓存为什么会出问题?
不少 CDN 和图片服务支持“同一个 URL,按浏览器能力返回不同格式”:读请求的 Accept 头,带 image/avif 就回 AVIF,只带 image/webp 就回 WebP,否则回原图。这样不用改 HTML,但缓存层面有三个坑。
坑一:没有 Vary,缓存会把错误的格式发给别人
同一个 URL 对应多种内容,响应就必须带 Vary: Accept,告诉所有中间缓存“这个响应和 Accept 头有关”。如果源站或图片服务做了格式协商却没加 Vary,某一层共享缓存(CDN、公司代理、反向代理)先存下 AVIF 版本,之后就可能把它发给不支持 AVIF 的旧浏览器,结果就是图片裂开。
坑二:有 Vary: Accept,命中率却掉下来
反过来,如果 CDN 老老实实按原始 Accept 头的完整字符串区分缓存,问题又来了:不同浏览器、不同版本发出的 Accept 值五花八门,每种写法都会单独占一份缓存,同一张图在边缘被存了很多份,命中率下降,回源变多。
正确做法是让 CDN 把 Accept 归一化成少数几档(例如 avif / webp / 原格式三档),再放进缓存键。具体怎么配要看你用的 CDN:有的产品在开启图片转换时自动处理,有的需要在缓存键规则里手动加一条,有的对 Accept-Encoding 以外的 Vary 支持有限,可能直接不缓存。动手前先读一遍所用 CDN 的官方文档里“缓存键”和“Vary”两节。
坑三:浏览器缓存和 CDN 缓存不一致
图片 URL 带了内容 hash,就可以给长缓存:Cache-Control: public, max-age=31536000, immutable。但格式协商的 URL 通常不带 hash,原图更新后,各档格式的缓存需要一起清理,只清原图会出现“Chrome 看到新图、旧浏览器看到旧图”的情况。缓存头的完整配法可以看Cache-Control 缓存头怎么配。
用 curl 检查 CDN 是否按格式正确缓存
URL=https://example.com/img/hero.jpg
# 模拟支持 AVIF 的浏览器
curl -sI -H "Accept: image/avif,image/webp,*/*" $URL | grep -iE "content-type|vary|cache|age"
# 模拟只支持 WebP 的浏览器
curl -sI -H "Accept: image/webp,*/*" $URL | grep -iE "content-type|vary|cache|age"
# 模拟都不支持的客户端
curl -sI -H "Accept: */*" $URL | grep -iE "content-type|vary|cache|age"
检查要点:
- 三次请求的
Content-Type分别是image/avif、image/webp和原格式。 - 响应带
Vary: Accept,或者 CDN 文档明确说明已在缓存键中区分格式。 - 每条命令连续执行两次,第二次的缓存状态头(不同 CDN 名字不同,如
cf-cache-status、x-cache)显示命中。 - 第三条命令拿到的不是 AVIF/WebP。如果是,说明协商或缓存键有问题,旧浏览器会裂图。
如果不想承担这些风险,构建时生成多格式文件,再用 <picture> 明确引用,URL 和格式一一对应,缓存最简单。CDN 自动转换适合图片量大、来自用户上传、改 HTML 成本高的场景。
图片优化之外,还要看哪些环节?
图片压小以后,页面还慢,问题多半在网络链路上:CDN 节点离用户远、回源慢,或者第三方资源阻塞了渲染。国内访问的情况可以参照:
- 托管在海外静态平台的站点,可以按静态网站国内访问慢的诊断方法逐层排查。
- 字体也是首屏大头,处理方法见Google Fonts 自托管、子集化与 font-display。
- 用 Cloudflare 做缓存和回源保护的配置思路,可以参考用 Cloudflare 加速 AI 应用,其中缓存规则部分同样适用于图片。
选公共前端 CDN 时,可以先在 cdnping 在线测速 里看你所在网络到各个公共 CDN 的首次连接和往返时间,再对照 CDN 速度榜 和国内如何选择公共 CDN。需要把自有站点放到边缘网络加速的,cdnping Edge 有服务说明;注意中国大陆境内加速需要 ICP 备案,并经由持证服务商。
上线前检查表
- 照片类图片有 AVIF、WebP、JPEG 三档,截图类至少有 WebP 和 PNG。
-
<source>顺序是 AVIF 在前、WebP 在后。 -
sizes与 CSS 中的实际显示宽度一致。 - 所有
<img>都写了width、height和alt。 - 首屏主图没有
loading="lazy",带fetchpriority="high"。 - 首屏以下的图片都加了
loading="lazy"。 - 抽查几张图,确认 AVIF 不比 WebP 大,文字边缘清楚。
- 如果用 CDN 自动转换,三种
Accept的 curl 检查都通过。 - 带 hash 的图片 URL 用长缓存加
immutable。
做完这些,用 DevTools 的 Network 面板按 Img 过滤,看实际下载的格式和大小,再用 Lighthouse 看 LCP 和 CLS 有没有变化。数字以你自己站点的实测为准。