画像と動画の配信 — 軽くしたはずの画像が、ページを遅くしている
全部の画像を WebP にして遅延読み込みを付けたら、LCP が悪化した。効くのは形式より画素数で、遅延読み込みは付ける場所を間違えると逆に遅くする。
この記事の進み方
What — 画像が表示されるまで
<img> を1つ書くと、ブラウザは次の順で処理します。どこで時間とメモリを使うかは、工程ごとに違います。
- 1HTML の中で見つけるプリロードスキャナが先読みする軽い
- 2取得を始めるか決める遅延読み込みならここで待つ中
- 3候補を選んで取得するsrcset から1つ選ぶ中
- 4展開する縦 × 横 × 4バイト重い
- 5レイアウトして描く大きさが分かるのはここ中
冒頭の事故は、2 で遅延読み込みが取得を止め、4 で 4000px の展開が端末を重くしていた。どちらも 3 の転送量とは別の工程。
転送量は「取得」の工程にしか効かない。形式を変えても、見つけるのが遅ければ遅く、画素が多ければ展開で重い。
- srcset
- 同じ画像の、大きさ違いの候補を並べる属性。ブラウザが表示幅と画素密度から1つ選ぶ。
- sizes
- その画像が画面上で何 px 幅で表示されるかを、ブラウザに先に伝える属性。
- DPR
- Device Pixel Ratio。CSS の 1px に、端末の画素が何個並ぶか。多くのスマートフォンで 2 か 3。
- プリロードスキャナ
- HTML の解析と並行して、先に取得すべき画像やスクリプトを拾う仕組み。
ブラウザは、レイアウトを知らないまま候補を選ぶ
srcset を書いても、ブラウザが小さい画像を選んでくれるとは限りません。
ブラウザが候補を選ぶのは、CSS を適用してレイアウトを計算するより前です。早く取得を始めたいので、「この画像が画面で何 px になるか」を待たずに決めます。そのための手がかりが sizes です。
<img
src="shoe-800.jpg"
srcset="shoe-400.jpg 400w, shoe-800.jpg 800w, shoe-1200.jpg 1200w, shoe-2400.jpg 2400w"
sizes="(max-width: 600px) 50vw, 300px"
width="800" height="800" alt="…">
sizes が無いと、ブラウザは画面幅いっぱい(100vw)に表示されると見なします。2列のグリッドで1枚あたり半分の幅しか使わない画像でも、全幅ぶんの候補を選びます。
必要な画素数は 表示幅 × DPR です。幅 390px の画面いっぱいに出すなら、DPR 3 の端末では 1170 画素ぶんが要ります。
幅 390px・DPR 3 のスマートフォンで、2列の商品グリッドを表示します。1枚は約190px 幅です。srcset に 400w / 800w / 1200w / 2400w を用意しましたが、sizes を書き忘れました。ブラウザが選びやすいのはどれですか?
Why — 何が効き、何が効かないのか
画像の最適化は、どの工程のコストを下げるかで考えます。「全部の画像に同じ処理をする」と、効く工程と効かない工程が混ざり、効かない場所では害になります。
形式より、画素数が効く
WebP や AVIF は、同じ画質なら JPEG より小さくなります。転送量は確かに減ります。
ただし、減るのは「取得」の工程だけです。展開した後の大きさは、形式に関係なく 縦 × 横 × 4バイトです。
4000 × 3000 の画像 JPEG 2.4MB → 展開 48MB
WebP 1.6MB → 展開 48MB ← 形式を変えても同じ
800 × 600 の画像 JPEG 120KB → 展開 1.9MB ← 画素を減らすと両方減る
幅 390px の端末に 4000px を配っても、表示されるのは縮小した結果です。取得と展開のコストを払って、画面には出ない画素を捨てています。 形式の変換より先に、表示に必要な大きさの候補を用意するほうが、転送量にも展開にも効きます。
遅延読み込みは、画面外のためのもの
loading="lazy" は、画面外の画像の取得を後回しにする指示です。ページ下部のレビュー画像を最初に取らなくて済むので、最初の表示が軽くなります。
後回しにするには、その画像が画面から遠いと分かる必要があります。分かるのはレイアウトの後です。そのため、最上部の画像に付けると、
遅延読み込み無し: HTML を解析中に src を見つける → すぐ取得を開始
遅延読み込みあり: HTML を解析 → CSS を待つ → レイアウト → 画面内と判定 → ここで取得を開始
見えている画像の取得が、レイアウトの完了まで待たされます。 それが LCP の要素なら、LCP がそのまま遅れます。冒頭の 2.1秒 → 3.4秒はこれです。
最上部の画像には逆に、fetchpriority="high" を付けて優先して取るよう伝えます。
最優先で取る。遅延読み込みは付けない
<img src=… srcset=… sizes=… fetchpriority="high" width=… height=…>- loading
- 付けない(既定の eager)
- fetchpriority
- high
- 置き方
- HTML に直接書く。CSS 背景や JS で差し込まない
- 効く指標
- LCP
- 注意
- —
- 場所の確保
- —
- 直し方
- —
- CSS で幅を変える場合
- —
- 発見
- —
- srcset
- —
- 向いているもの
- —
- LCP になると
- —
- –遅延読み込みを付けると、レイアウトの完了まで取得が始まらない
- –CSS の背景画像はプリロードスキャナに見つからず、発見が遅れる
- –スライダーの2枚目以降は主役ではない。1枚目だけこの扱い
1ページの中でも、画像の役割ごとに指定を変える。全部に同じ属性を付けると、どこかで必ず逆効果になる。
ページ最上部の大きな商品画像(LCP の要素)に loading='lazy' を付けたところ、LCP が悪化しました。なぜですか?
動画は、画像より桁が違う
アニメーション GIF は、動画を画像の形式で無理に表現したものです。圧縮の仕組みが古く、数秒の動きでも数 MB になります。同じ内容を動画の形式(MP4 / WebM)にすると、数分の一から十分の一程度になることが珍しくありません。
動画には、画像に無い判断があります。
preload— ページを開いた時点でどこまで取るか。noneなら再生まで取らない、metadataなら長さと大きさだけposter— 再生前に出す静止画。これが最初の表示になるので、LCP の候補にもなる- 自動再生 — 多くのブラウザは、音が出ない(
muted)場合に限って自動再生を許す。iPhone では画面いっぱいに開かないようplaysinlineも要る
長い動画を1つのファイルで配ると、通信が細い利用者は再生が止まり、太い利用者は画質を持て余します。回線に合わせて画質を切り替える配信(アダプティブストリーミング)が必要になるのは、この差のためです。
演習 — まず自分で判断する
解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。
この商品詳細ページで、画像の配信をどう組みますか?
- 商品画像は出品者がアップロードする。最大 6000px、JPEG と PNG が混在
- 表示はスマートフォンで幅いっぱい、PC では 600px 幅
- 最初の画面に、メイン画像1枚とサムネイル8枚。下にレビュー画像が数十枚
- 利用者の7割がスマートフォンで、通信量を気にする層が多い
- CLS も悪く、画像が届くたびに購入ボタンの位置がずれる
- · 転送量・発見の早さ・展開の重さ・場所の確保を、どの工程で解決するか
- · 画像の役割(主役・最初の画面・画面外)ごとに、指定を変えるか
- · 出品者がアップロードする大きさを、誰がどこで揃えるか
「画像は全部圧縮したのに、古いスマートフォンで商品ページがカクつく」と言うデザイナーに、何が起きているかを説明してください
- 相手は画像の書き出しと圧縮には詳しい
- アップロードしているのは 4000px の画像で、ファイルは 400KB 程度まで圧縮済み
- 開発側で何を変えるか、デザイナー側で何を変えるかを分けて伝えたい
読み終わりましたか?
読了にすると、これを前提とする記事がロードマップで開放されます。