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 有沒有變化。數字以你自己站點的實測為準。