解像度ベース Progressive AVIF を運用するための技術記事 を使う
のが結論です。
3. 解像度ベース layered AVIF の正しい作り方
例:1/8 → 1/2 → 1/1 の3レイヤ
4. 「異なる解像度の画像を渡すと落ちる」根拠(=制約の理由)
--layered は **“各入力が1レイヤになる”**モードです。manpage にもそのまま書かれています。(manpages.debian.org)
この実装では「レイヤは同一キャンバス上に重ねる」扱いなので、入力画像の寸法(幅・高さ)が揃っていないと Image layer dimensions mismatch で弾かれます。
ここで混乱しがちなのが --scaling-mode の意味で、これは「入力画像そのもののピクセル寸法を変換する」というより、そのレイヤを“どのスケールとして扱うか”を指定するモードです(つまり “レイヤの解釈” の情報)。(GitHub)
5. サポートされるスケールの目安(なぜ 1/16 が落ちる?)
--scaling-mode は libavif 側でも “experimental” 扱いで、リリースノートでも「この部分は十分テストされていない/クラッシュの可能性」まで書かれています。(GitHub)
また、Jake の記事でも「特定の scaling 値しか許されない」ことに触れられています。(jakearchibald.com)
になりがちで、「現状の libavif+libaom の実装都合として 1/8 が下限になりやすい」という理解が安全です(AVIF仕様が1/8を規定しているわけではありません)。
6. Astro への組み込み:重要なのは “再エンコード回避”
Astroのアセットパイプライン(Sharp等)で再処理が入ると、layered 情報が失われる可能性が高いです。
OK:public/ に置いて静的パス参照
public/ はそのまま配信されるので、layeredを保持したまま出せます。
7. プロジェクト構成(例)
8. 変換スクリプト(例)
9. 検証方法(CLI)
レイヤ有効化の確認
各レイヤを抽出
10. ブラウザでのテスト(Chrome推奨)
段階表示が効く環境では 低解像度 → 中解像度 → 高解像度の“上書き”が見えます。
重要:preload は慎重に
preloadで先に全部取りに行くと、「段階的に届く」メリットが体感しにくくなります(特に高速回線)。
LCPを狙う場合も「最終レイヤを preload してしまう」など、狙いと逆効果になりがちなので、計測しながら調整が安全です。
11. 依存関係
12. トラブルシューティング
Image layer dimensions mismatch
--layered は 各入力が1レイヤなので、入力寸法が揃っていないと弾かれます。(manpages.debian.org)
→ 同じ input を複数回渡して --scaling-mode を変える方式に寄せる。
Not implemented / 小さすぎるスケールで失敗
--scaling-mode は experimental で、許される値が限られます。(jakearchibald.com)
→ 現状は 1/8 を下限に設計するのが安全。
Progressive が見えない
参考
必要なら、この内容をベースに 「実際の convert-to-progressive-avif.js を“そのままコピペで動く完成形”(globで収集→PNG正規化→avifenc呼び出し→差分ビルド→ログ出力)にして記事内に載せるところまで仕上げます。
- 体感速度の向上:まず“見える”状態になるのが早い
- LCP 改善の可能性:最初のレイヤがLCPに間に合うケースがある(ただし計測要注意)
- 1ファイル完結:LQIPのように「別プレースホルダ画像」を管理しなくてよい
- “段階描画”が効くのは主に Chromium 系(Chrome 94+ 等)。libavifのリリースノートでも「Chrome 94+ などの対応ビューア」と明記されています。(GitHub)
--layered / --scaling-mode は EXPERIMENTAL。落ちる設定があったり、挙動が変わる可能性がある前提で扱う必要があります。(GitHub)
| オプション | 役割 | 期待される結果 |
|---|
--progressive | 自動で「単純な layered」を設定するプリセット | 品質ベース(同解像度で品質だけ違う)になりやすい |
--layered + --scaling-mode | レイヤ(各入力)を明示し、スケールも指定できる | 解像度ベース(1/8 → 1/2 → 1/1 など)を狙える |
--codec aom を明示(layered エンコードは当時 “aomのみ対応” と libavif 側が明記)([GitHub][2])同じ入力ファイルを複数回渡すレイヤごとに --scaling-mode と -q を指定(:u は “以降に適用”)✅ 正:同じ input.png を複数回渡して、--scaling-mode 1/8 などを指定する❌ 誤:1/8に縮小したPNG(1521x1014)とフルPNG(6086x4057)を別入力として渡す1/2, 1/4, 1/8 … OK1/16, 1/32 … NG(Not implemented / failed)DevTools → Network → Slow 3GDisable cache をONHard Reload(Ctrl+Shift+R)public/ 配信になっているか(Astro再処理を回避できているか)Chromiumで見ているか(段階表示は主にChromium側の実装に依存)(GitHub) DevToolsで回線を絞っているか(速すぎると一瞬で終わる)Jake Archibald, The present and potential future of progressive image rendering (jakearchibald.com) libavif Releases / layered + scaling-mode の注意事項(Chrome 94+ / aomのみ / experimental)(GitHub) - sharp 等で「入力を一度PNG化」してから
avifenc に渡す、などの運用も可
avifenc --codec aom --layered -s 6 \
--scaling-mode 1/8 -q 20 input.png \
--scaling-mode:u 1/2 -q:u 50 input.png \
--scaling-mode:u 1 -q:u 70 input.png \
output.avif
avifenc --codec aom --layered -s 6 \
--scaling-mode 1/8 -q 20 input.png \
--scaling-mode:u 1/2 -q:u 50 input.png \
--scaling-mode:u 1 -q:u 70 input.png \
output.avif
// NG: 再処理されやすい
const images = import.meta.glob('../assets/img/gallery/*.avif', { eager: true });
// OK: public/ から静的パス参照
const images = [
'/img/gallery/img1.avif',
'/img/gallery/img2.avif',
];
akitoshi-lab.com/
├── scripts/
│ └── convert-to-progressive-avif.js
├── src/assets/img/
│ └── gallery/
│ ├── *.webp, *.jpg, *.jpeg # 元画像
├── public/img/
│ └── gallery/
│ └── *.avif # 生成物(layered AVIF)
└── docs/
└── progressive-avif.md
const CONFIG = {
inputDirs: ['src/assets/img/gallery', 'src/assets/img'],
extensions: ['.jpg', '.jpeg', '.png', '.webp'],
progressive: {
layer0: { scale: '1/8', quality: 20 },
layer1: { scale: '1/2', quality: 50 },
layer2: { scale: '1', quality: 70 },
},
speed: 6,
codec: 'aom',
};
avifdec --progressive --info image.avif
avifdec --progressive --index 0 image.avif layer0.png
avifdec --progressive --index 1 image.avif layer1.png
avifdec --progressive --index 2 image.avif layer2.png