WordPressの画像最適化について
このような局面に遭遇した事はありませんか?
picture srcset=~ って長たらしいソースコードがあるけど、これってなに?
コアウェブバイタルに影響ないのかな?
そこで実際に検証してみます。なお本検証は、max-widthによってコンテンツ幅が制御されている一般的なWebサイトを対象とします。
picture要素やsrcset自体の優劣を論じるものではなく、
表示サイズを大きく超える画像配信がブラウザ内部の処理負荷へ与える影響を検証するものです。
-
- img src タグにて設定
- width及びheight を適切なアスペクト比で記述。
- 画像にはWebPを使用。
- fetchpriority=”high” loading=”lazy” 及び alt=”○○” も適切に使用
- リソースに表示される画像サイズにあらかじめリサイズ
-
- picture srcset= 複数画像 を使用
- width height
- fetchpriority=”high” loading=”lazy” 及び alt=”○○” も適切に使用
① VS ② の場合、CLS・INP・LCP にてどちらが優位性があるか?
①(単一最適化) VS ②(複数解像度最適化)ブラウザ内部の「物理的挙動」比較検証
画像が画面に表示されるまでには、デバイスのハードウェア(CPU、RAM、GPU)において
「パース(解析)」「計算」「通信」「確保」「デコード(展開)」「レンダリング(描画)」
という物理的なステップが存在します。
iPhone 15 Pro(画面幅:393px / デバイスピクセル比 DPR = 3)で、
画面上の表示幅が 800×450px の画像を読み込むシーンを例に、
ハードウェアの動きを比較します。
物理フェーズ1:HTMLの解析とダウンロード判断(CPUの仕事)
ブラウザがHTMLコードを上から順番に読み込む(パースする)瞬間の挙動です。
<!-- ① のHTML(極限までシンプル) -->
<img src="test-800.webp"
width="800"
height="450"
alt="..."
fetchpriority="high">
<!-- ② のHTML(入れ子構造と複数の属性値) -->
<picture>
<source
srcset="test-320.webp 320w,
test-800.webp 800w,
test-2400.webp 2400w"
sizes="(max-width: 1200px) 100vw, 1200px"
type="image/webp">
<img
src="test-800.webp"
width="800"
height="450"
alt="..."
fetchpriority="high">
</picture>
【①:CPU負荷=ほぼゼロ】
挙動:
HTMLパースと並行して動く「プリロードスキャナー(先読み機構)」が、
1本道で test-800.webp を検出し、
直ちにネットワークリクエストを発行します。
CPUは何の迷いも計算もなく、
ストレートにファイルをダウンロードしに行きます。
【②:CPU負荷=大(計算待ちの発生)】
挙動:
ブラウザはまず、
<picture> というDOM階層を構築します。
次にCPUは、
「現在のiPhoneの画面幅 393px」
「DPRが3であること」
「sizes属性で定義された100vw」
を照らし合わせ、
必要な解像度は物理的に
393 × 3 = 1179px
であると計算します。
さらに srcset の中から
「1179px以上で、最もこの値に近い画像」
を走査し、
test-2400.webp(2400px幅の巨大画像)
を選択します。
物理的な差:
②はダウンロードを開始する前に、
CPUによるビューポート計算と条件分岐処理が物理的に割り込むため、
ミリ秒単位でパケットロスのリスクや
ネットワーク接続の遅延(LCPの遅れ)が発生します。
物理フェーズ2:メモリ(RAM/VRAM)の確保(メモリの仕事)
ここが最も「物理的」かつ、
多くのコーダーが見落としている最大のボトルネックです。
WebP画像は「圧縮された状態」で通信され、
画面に表示される瞬間に
「生のビットマップデータ」
にデコード(解凍)されます。
デコードされた画像がデバイスのメモリ(RAM)上で消費する物理的なメモリ量は、
以下の数式で決定されます。
消費メモリ量 = 画像の幅 × 画像の高さ × 4 bytes(RGBA各色1バイト)
【①:メモリ消費量 = 1.44MB】
あらかじめ表示サイズ(800×450)にリサイズされているため、
デコードされるサイズもそのままです。
800 × 450 × 4 bytes
= 1,440,000 bytes
≒ 1.44 MB
結果:
スマホのメモリ空間をほとんど汚さず、
極めて軽量に描画領域が確定します。
【②:メモリ消費量 = 12.96MB(①の9倍重い)】
iPhoneはDPR(画素密度)が3なので、
少しでも綺麗に表示しようとして、
srcset の中から
2400×1350 の超高解像度画像を自動で選択してしまいます。
2400 × 1350 × 4 bytes
= 12,960,000 bytes
≒ 12.96 MB
結果:
ファイルサイズとしてのWebPは100KB程度に圧縮されていても、
スマホのブラウザが画面に表示するためには、
物理的に約13MBものメモリを確保して展開しなければなりません。
物理的な差:
画像が1枚なら耐えられますが、
ページ内に画像が10枚あれば、
メモリ消費量は
14.4MB(①) vs 129.6MB(②)
にまで跳ね上がります。
格安スマホや古いiPadなどの場合、
このメモリ不足によってブラウザがクラッシュしたり、
極端な速度低下を引き起こします。
物理フェーズ3:デコード処理とメインスレッドの占有(CPU/GPUの仕事)
メモリに確保したRAWデータを、
画面のピクセルへと描き出す
「デコード(解凍)」のフェーズです。
【①:メインスレッドは常に快適(INP最高値)】
挙動:
デコード対象が1.44MB分(36万画素)しかないため、
CPUのサブスレッドが非同期(decoding=”async”)で
あっという間に解凍を終えます。
影響:
メインスレッド(JavaScriptを実行し、
ユーザーのタップを検知するメインの脳ミソ)を
一切邪魔しません。
ユーザーがボタンをタップした瞬間に、
ブラウザは1msも遅れることなく反応(INP = Good)します。
【②:メインスレッドの瞬間フリーズ(INP悪化の原因)】
挙動:
デコード対象が12.96MB分(324万画素)に膨れ上がっているため、
いくら非同期(async)を指定していても、
スマートフォンのCPUコアが100%まで張り付き、
GPUへのテクスチャ転送でボトルネックが発生します。
影響:
画像が画面に表示される瞬間、
スマートフォンの中で
「一瞬の処理待ち(カクつき)」
が発生します。
ユーザーがこの瞬間に
スマホのハンバーガーメニューをタップしても、
反応が数ミリ秒〜数十ミリ秒遅れてしまいます。
これがINP(操作応答性)の悪化の物理的メカニズムです。
物理フェーズ4:レイアウトマップとCLSの完全防止
CSSが読み込まれる前に、
ブラウザが画面の「予約席」を作るフェーズです。
【①:完全なる1対1予約(CLS = 0.00)】
<img width=”800″ height=”450″>
という単純な情報から、
ブラウザは即座に
「縦横比(アスペクト比)16:9」
の計算式を決定します。
外部CSS(スタイルシート)のロードが遅れていても、
HTMLをパースしたその瞬間に、
画面上に16:9比率の空間を物理的に確保します。
後から画像が降ってきても、
下部コンテンツは1ピクセルもズレません。
【②:切り替え時のレイアウト再計算リスク】
<picture> の場合、
メディアクエリ(画面幅)によって
読み込むソースファイルが異なるため、
ブラウザは
「現在のソースファイルが確定するまで、
どのアスペクト比を適用すべきか」
を迷う(投機的レイアウトの発生)ことがあります。
モバイル端末を縦から横に回転させた際などに、
画像の再ダウンロードと同時に、
HTMLのアスペクト比が再計算され、
一瞬画面が「ガクッ」と動く(CLSの発生)
リスクを物理的に孕んでしまいます。
結論:物理レイヤーでのジャッジ
ネットワーク帯域(通信量)だけを気にすれば、
②(picture / srcset)は一見
「適材適所のスマートな技術」
に見えます。
しかし、ダウンロードした後の
「スマホ内部での処理負荷(CPU・メモリ・デコードコスト)」
まで含めたトータルパフォーマンスにおいては、
事前にジャストサイズに物理リサイズされた
①(単一最適化)が、
圧倒的に「エコで、軽くて、速い」
のです。