← 回 knowledge index

Knowledge Mirror

AVIF 圖片格式落地:能力檢測、CDN 快取與異步轉碼設計

來源筆記:AVIF 圖片格式落地:能力檢測、CDN 快取與異步轉碼設計.md

SegmentFault:下一代圖片格式 AVIF 在 vivo 社區的落地實踐 SegmentFault 文章 整理日期:2026-05-08 這篇筆記整理一套在內容型網站落地 AVIF 圖片格式的工程方案。 核心不是單純「把圖片改成 AVIF」,而是建立一條安全、可回退、可快取、可觀測的圖片交付鏈路:…

AVIF 圖片格式落地:能力檢測、CDN 快取與異步轉碼設計

#avif #web-performance #image-optimization #cdn #async-processing #frontend #backend #webp #lcp #obsidian

來源

SegmentFault 文章

這是什麼

這篇筆記整理一套在內容型網站落地 AVIF 圖片格式的工程方案。

核心不是單純「把圖片改成 AVIF」,而是建立一條安全、可回退、可快取、可觀測的圖片交付鏈路:

前端能力檢測
→ 選擇最佳圖片格式
→ CDN 優先命中
→ 後端檢查衍生檔是否存在
→ 不存在則觸發異步轉碼
→ 前端先降級 WebP / JPG
→ 轉碼完成後後續請求命中 AVIF

這種做法適合圖片流量大、首屏圖片多、弱網體驗重要,且已經使用 WebP 但還想進一步壓縮體積的網站。

核心結論

AVIF 的價值在於更好的壓縮效率,但真正能安全上線的關鍵是四件事:

  1. 能力檢測:不要靠 user-agent 猜瀏覽器支援度,而是實測瀏覽器能不能解碼 AVIF。
  2. 前端降級:AVIF 載入失敗時自動退回 WebP,再不行退回 JPG,避免圖片破圖。
  3. CDN 快取:同一張圖轉碼後要能被 CDN 邊緣快取,避免重複回源與重複計算。
  4. 異步轉碼:不要在使用者請求鏈路上即時轉 AVIF,因為 AVIF 編碼成本高;首次 miss 時應排背景任務,使用者先看 fallback。

一句話:AVIF 是格式優化,落地時其實是在做一套圖片衍生檔生成與交付系統。

為什麼選 AVIF

AVIF 基於 AV1 編碼,通常在相同主觀畫質下比 JPEG / WebP 有更好的壓縮率。

vivo 社區的案例中:

但 AVIF 不是所有場景都必然更好。小圖、透明 PNG、文字海報、動畫圖,都需要另外測試收益與畫質。

能力檢測是什麼

能力檢測是前端確認「目前瀏覽器真的能顯示 AVIF」。

不要只看瀏覽器名稱或 user-agent,因為:

更可靠的做法是:建立一個 Image(),塞一張極小的 AVIF base64 測試圖,讓瀏覽器實際解碼,然後檢查圖片尺寸是否符合預期。

能力檢測怎麼做到

概念範例:

let avifSupportPromise;

function supportsAvif() {
  if (avifSupportPromise) return avifSupportPromise;

  avifSupportPromise = new Promise(resolve => {
    const img = new Image();
    img.onload = img.onerror = () => {
      resolve(img.height === 2);
    };
    img.src = 'data:image/avif;base64,...'; // 極小 AVIF 測試圖
  });

  return avifSupportPromise;
}

關鍵點:

怎樣做才有效率

推薦策略:

  1. App 初始化時測一次。
  2. 把結果存在全域狀態或 Promise,避免重複測。
  3. 同一個 session 內直接重用結果。
  4. 若要跨 session 快取,可放 localStorage,但要有版本或 TTL。

範例:

async function getImageFormat() {
  const cached = localStorage.getItem('img-format:v1');
  if (cached) return cached;

  const ok = await supportsAvif();
  const format = ok ? 'avif' : 'webp';

  localStorage.setItem('img-format:v1', format);
  return format;
}

更保守的做法是只長期快取成功結果,失敗結果短期快取即可。原因是某些 WebView、PWA 或初始化時機問題可能造成偶發檢測失敗,不一定代表永久不支援。

也可以由後端根據 Accept header 判斷格式:

Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8

但實務上前端能力檢測仍有價值,因為它更接近「這個 runtime 是否真的能 decode」。大型系統也可以採混合策略:後端看 Accept 做初步協商,前端再用 decode test 與 onerror fallback 保底。

URL 與圖片格式選擇

前端取得格式後,組出穩定 URL:

function getOptimizedImageUrl(imageId, format) {
  return `https://cdn.example.com/images/${imageId}.${format}`;
}

也可以用 query string:

https://cdn.example.com/images/abc?format=avif&w=800&q=75

設計重點:

前端降級策略

AVIF 請求失敗時,前端應該自動退回 WebP;WebP 也失敗時,再退回 JPG。

概念範例:

<img id="cover" src="/images/abc.avif" />

<script>
const img = document.querySelector('#cover');

img.onerror = function () {
  const current = this.src;

  if (current.endsWith('.avif')) {
    this.src = current.replace(/\.avif$/, '.webp');
    return;
  }

  if (current.endsWith('.webp')) {
    this.src = current.replace(/\.webp$/, '.jpg');
  }
};
</script>

實際產品中可以封裝成 Image component,統一處理:

異步轉碼如何處理

AVIF 編碼通常比 WebP 慢,不適合在使用者請求當下即時轉碼。比較穩的方式是「miss 時背景補貨」。

流程:

Browser
  ↓ request abc.avif
CDN
  ↓ cache miss
Image API
  ├─ AVIF exists? yes → return avif
  └─ AVIF exists? no
      ├─ enqueue convert job
      └─ return 404 / 202 / fallback

Worker
  ↓ consume job
Original image storage
  ↓ convert to AVIF
AVIF storage
  ↓
CDN cache on next request

使用者第一次看到的可能是 WebP,背景轉碼完成後,下一次或下一位使用者就會吃到 AVIF。

異步轉碼的後端設計

一個實用的後端可以拆成:

Object Storage:保存原圖與衍生圖
Image API:處理圖片請求、檢查衍生檔狀態、觸發任務
Queue:保存轉碼任務
Worker:執行轉碼
Metadata DB:保存圖片衍生檔狀態
CDN:快取衍生圖

狀態表可以長這樣:

image_id
format
width
quality
status: pending | done | failed
source_etag
output_url
error_message
retry_count
created_at
updated_at

圖片請求時:

  1. 根據 image_id + format + width + quality 算出衍生檔 key。
  2. 檢查 storage 是否已有該檔。
  3. 有就回傳或 redirect 到 CDN URL。
  4. 沒有就檢查狀態表。
  5. 若沒有 pending 任務,建立任務。
  6. 回傳 404 / 202 / fallback,讓前端降級。

任務去重與鎖

異步轉碼最重要的是避免同一張圖被大量併發請求觸發重複轉碼。

可用 Redis lock:

SETNX convert:abc:avif:800:q75 1 EX 300

拿到鎖才 enqueue 任務;拿不到鎖代表已經有人排隊或正在轉。

也可以靠資料庫唯一索引:

UNIQUE(image_id, format, width, quality, source_etag)

如果 insert pending 失敗,就代表任務已存在,不再重複排。

Worker 轉碼流程

Worker 的基本流程:

1. 取出 queue job
2. 下載原圖
3. 檢查圖片類型、尺寸、是否適合轉 AVIF
4. 執行轉碼
5. 可選:計算 PSNR / SSIM 或抽樣做品質檢查
6. 上傳 AVIF 到 storage
7. 更新 metadata status = done
8. 清除 lock

失敗時:

品質與轉碼參數

AVIF 常見會用 CRF / quality 類參數控制體積與畫質。vivo 案例提到 CRF=32、CRF=35 的調優比較,CRF35 體積更小但 PSNR 略降。

上線前應該分層測試:

不要只看平均壓縮率,因為不同圖片類型收益差很多。

什麼圖不一定適合 AVIF

以下類型要特別小心:

可建立規則:

小於 20KB:不轉
寬高太小:不轉
透明 PNG:保守參數或跳過
文字比例高:較高品質參數
大圖:優先轉 AVIF

上線觀測指標

要判斷 AVIF 是否真的有效,至少觀測:

如果 AVIF fallback 率很高,可能代表:

最小可行版本

第一版不要做太滿,可以先做:

前端:AVIF 能力檢測 + onerror fallback
後端:AVIF 不存在就 enqueue 任務
Worker:轉 AVIF 寫回 storage
CDN:快取轉好的 AVIF
觀測:記錄 fallback 與轉碼成功率

這樣已經能驗證核心收益。

等第一版穩定後,再補:

與傳統即時轉碼的差異

不建議:

使用者請求圖片 → 後端立即轉 AVIF → 等轉完才回應

原因:

建議:

使用者請求 AVIF → 沒有就排背景任務 → 使用者先看 WebP → 下次命中 AVIF

這個策略把 CPU 成本移出請求鏈路,用 CDN 快取攤平成本。

一句話設計準則

AVIF 落地不要只想「改副檔名」。應該把它當成圖片服務能力升級:

正確檢測支援度
穩定產生衍生檔
用 CDN 放大轉碼收益
用 fallback 保證可用性
用觀測數據決定是否擴大