WebP AVIF 區別怎麼選:picture 標籤與 CDN 自動轉換

部落格 / 效能

WebP AVIF 區別怎麼選:picture 標籤與 CDN 自動轉換

WebP 和 AVIF 的區別,簡單說是:AVIF 在同等觀感下通常更小,尤其是照片,但編碼慢、個別場景下文字邊緣會糊;WebP 體積比 JPEG/PNG 小,編碼快,相容面更寬,對截圖和帶透明的圖更穩。所以實際做法通常是兩種都出,用 <picture> 讓瀏覽器自己挑,再用 JPEG/PNG 兜底。

格式選對只解決了一半。另一半是尺寸和載入順序:給手機發一張 2400px 寬的 AVIF,照樣是浪費。下面按圖片型別給建議,再給可以直接複製的 HTML 模板、批次轉換指令碼,最後講 CDN 自動轉格式時容易踩的快取坑。

WebP 和 AVIF 到底差在哪?

兩者都是為了替代 JPEG 和 PNG 設計的格式,都支援有損、無損和透明通道。差別主要在下面幾處:

對比項WebPAVIF
編碼基礎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 有損 → JPEGAVIF 收益最明顯的場景
介面截圖、帶大量文字的圖WebP 無損或高質量有損PNGAVIF 要用 4:4:4 色度抽樣並提高質量,否則字邊發虛
插畫、扁平色塊圖兩種都試PNG色塊大時無損 WebP 往往已經很小
帶透明的圖示、LogoSVGWebP 無損 → 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>

幾個容易寫錯的地方:

  1. <source> 的順序就是優先順序。瀏覽器從上往下找第一個支援的 type,所以 AVIF 必須寫在 WebP 前面。
  2. sizes 要和 CSS 實際寬度一致。上面的意思是:視口不超過 768px 時圖片佔滿屏寬,否則固定 768px。瀏覽器拿這個值乘以裝置畫素比,再從 srcset 裡挑最接近的一檔。sizes 寫錯,瀏覽器就會下載過大或過小的圖。
  3. width 和 height 必須寫在 <img> 上。寫原圖的寬高即可,瀏覽器據此算出寬高比,提前留位置,避免圖片載入完把正文往下頂(CLS 佈局偏移)。CSS 裡再加 img { max-width: 100%; height: auto; }。
  4. 屬性都放在 <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 節點離使用者遠、回源慢,或者第三方資源阻塞了渲染。國內訪問的情況可以參照:

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

相關文章

繼續閱讀