ファーストビューにアニメーションGIFが3つ並ぶマーケティングランディングページがありました — 製品の動きを見せるヒーローループに、機能アニメ2本。合計11MB。Lighthouseはモバイルで ページを64と評価し、LCPは4.2秒、Total Blocking Timeは380ms。3本のGIFを MP4に置き換えただけで — 同じビジュアル、同じループ挙動 — その日の午後のうちに92に乗りました。本記事は実況中継です。
出発点
ページは標準的なNext.jsマーケサイト。Tailwind、静的生成、Vercelデプロイ。3本のGIFはAfter Effectsから720p、30fps、適度な最適化で書き出したもの。サイズ:
- ヒーローアニメ (5秒、720p): 5.8 MB
- 機能1 (3秒、600p): 2.9 MB
- 機能2 (4秒、600p): 2.4 MB
アニメのペイロード合計: 11.1MB。Slow 4G (Lighthouseモバイルプロファイル) では完全ロードまで およそ27秒。テキストは早く描画されたものの、ヒーローはずっと「読み込み中」のプレースホルダ のままで、これがまさにLCPが罰する状態です。
出発時点のLighthouseレポート
| 指標 | スコア |
|---|---|
| Performance | 64 |
| Largest Contentful Paint | 4.2秒 |
| Total Blocking Time | 380ms |
| Speed Index | 5.8秒 |
| Cumulative Layout Shift | 0.04 |
変換
各GIFを2設定でffmpegに通しました: 広く使えるフォールバック向けにH.264 MP4、対応ブラウザで さらに小さくなるVP9 WebM。コマンド:
ffmpeg -i hero.gif -movflags faststart -pix_fmt yuv420p -crf 22 -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2:flags=lanczos" -an hero.mp4
ffmpeg -i hero.gif -c:v libvpx-vp9 -crf 30 -b:v 0 -deadline good -cpu-used 2 -row-mt 1 -pix_fmt yuv420p -an hero.webm
変換後のサイズは衝撃的でした:
| 素材 | GIF | MP4 (H.264) | WebM (VP9) |
|---|---|---|---|
| ヒーロー | 5.8 MB | 320 KB (-94%) | 240 KB (-96%) |
| 機能1 | 2.9 MB | 180 KB (-94%) | 140 KB (-95%) |
| 機能2 | 2.4 MB | 150 KB (-94%) | 110 KB (-95%) |
合計ペイロードはH.264で11.1MB → 650KB、VP9で490KB。17〜22倍の削減です。
マークアップの変更
元のマークアップは <img src="hero.gif" alt="...">。差し替え:
<video autoplay loop muted playsinline poster="hero-poster.jpg">
<source src="hero.webm" type="video/webm">
<source src="hero.mp4" type="video/mp4">
</video>
posterは動画がデコードを始める前に表示される静止フレーム(JPEG、約30KB)。 これがLCPの解錠ポイントでした — ブラウザがposterをcontentful paintとして認識し、1秒以内 に到達しました。
変換後のLighthouseレポート
| 指標 | 前 | 後 |
|---|---|---|
| Performance | 64 | 92 |
| LCP | 4.2秒 | 1.4秒 |
| TBT | 380ms | 110ms |
| Speed Index | 5.8秒 | 1.9秒 |
| CLS | 0.04 | 0.02 |
LCPは67%減、TBTは71%減。TBTが改善した理由: ソフトウェアGIFデコードがフレームごとに メインスレッドをブロックしていたから。ハードウェアMP4デコードは別プロセスへオフロードされます。
注意点と落とし穴
- autoplayにはmuted必須。iOSと2018年以降の大半のブラウザは音声付き動画の 自動再生をブロックします。autoplayループには常に
mutedを。 - iOSにはplaysinline必須。無いとiOSがタップ時に全画面プレイヤーで開きます。
- H.264は偶数寸法。コーデックは幅も高さも2の倍数を要求します。
scale=trunc(iw/2)*2:trunc(ih/2)*2フィルタを。 - Safariにはyuv420p必須。他のピクセルフォーマットはChromeでは通っても Safariで静かに失敗します。
- 静的な装飾に動画を使わない。「アニメ」がほぼ動かない(緩いズームや繊細な パン)なら、静止画やCSSアニメの方が安価です。
Lighthouseの先: 実ユーザーへの影響
Lighthouseの数値は合成スコア。本当に見るべきはフィールドデータ — 実際のネット越しの実際の 訪問者です。変更後2週間で見えたもの:
- 本番のp75 LCP: 3.8秒 → 1.6秒 (Chrome User Experience Report)
- 直帰率: モバイルで -8% (GA計測)
- CDN帯域請求: マーケサイト単体で -$340/月
帯域節約より、それまでパレット技や誤差拡散でGIFをさらに絞ろうと費やしたエンジニアリング 時間の方がはるかに高くついていました。フォーマットの境界を越えた瞬間、その努力はすべて 無関係になりました。
自分のサイトでやる方法
64から92へ連れて行った5ステップ:
- DevToolsのNetworkタブでページ上の
.gifをすべて見つける。 - それぞれをMP4 (H.264)とWebM (VP9)に変換。単発なら私たちのGIF → MP4 変換ツールが両フォーマットに対応し、 ブラウザ内で動きます。パイプラインなら上のffmpegコマンドを。
- 各動画に静止フレームのposter JPEGを生成。
ffmpeg -i input.gif -vf "select=eq(n\\,0)" -vframes 1 poster.jpg。 - 各
<img>を上記の<video>に置き換える。 - Lighthouseを再実行。スコアが跳ねたか、LCPが下がったかを確認。
所要時間: 私たちが管理するサイトで1ページあたり約20分。何千ものユーザーの全ページビューに 効いてくる複利効果のおかげで、今年やった性能修正の中でも単発で最もテコ入れの大きい仕事に なりました。