仕組みから学ぶ Web
応用読了目安 15#画像#動画#srcset#遅延読み込み#LCP

画像と動画の配信 — 軽くしたはずの画像が、ページを遅くしている

全部の画像を WebP にして遅延読み込みを付けたら、LCP が悪化した。効くのは形式より画素数で、遅延読み込みは付ける場所を間違えると逆に遅くする。

この記事の進み方

What — 画像が表示されるまで

<img> を1つ書くと、ブラウザは次の順で処理します。どこで時間とメモリを使うかは、工程ごとに違います。

Figure画像が画面に出るまで
  1. 1HTML の中で見つけるプリロードスキャナが先読みする軽い
  2. 2取得を始めるか決める遅延読み込みならここで待つ
  3. 3候補を選んで取得するsrcset から1つ選ぶ
  4. 4展開する縦 × 横 × 4バイト重い
  5. 5レイアウトして描く大きさが分かるのはここ

冒頭の事故は、2 で遅延読み込みが取得を止め、4 で 4000px の展開が端末を重くしていた。どちらも 3 の転送量とは別の工程。

1/5
HTML の中で見つけるブラウザは HTML を解析しながら、先回りして img の src を拾い、取得を始められる。CSS の背景画像や JS で差し込む画像は、この段階では見つからない。

転送量は「取得」の工程にしか効かない。形式を変えても、見つけるのが遅ければ遅く、画素が多ければ展開で重い。

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 を書き忘れました。ブラウザが選びやすいのはどれですか?

まず選ぶ(解答例は a〜d の記号で説明します)

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" を付けて優先して取るよう伝えます。

Compare画像ごとの読み込み方

最優先で取る。遅延読み込みは付けない

<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 が悪化しました。なぜですか?

まず選ぶ(解答例は a〜d の記号で説明します)

動画は、画像より桁が違う

アニメーション GIF は、動画を画像の形式で無理に表現したものです。圧縮の仕組みが古く、数秒の動きでも数 MB になります。同じ内容を動画の形式(MP4 / WebM)にすると、数分の一から十分の一程度になることが珍しくありません。

動画には、画像に無い判断があります。

  • preload — ページを開いた時点でどこまで取るか。none なら再生まで取らない、metadata なら長さと大きさだけ
  • poster — 再生前に出す静止画。これが最初の表示になるので、LCP の候補にもなる
  • 自動再生 — 多くのブラウザは、音が出ない(muted)場合に限って自動再生を許す。iPhone では画面いっぱいに開かないよう playsinline も要る

長い動画を1つのファイルで配ると、通信が細い利用者は再生が止まり、太い利用者は画質を持て余します。回線に合わせて画質を切り替える配信(アダプティブストリーミング)が必要になるのは、この差のためです。

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

この商品詳細ページで、画像の配信をどう組みますか?

与えられた条件
  • 商品画像は出品者がアップロードする。最大 6000px、JPEG と PNG が混在
  • 表示はスマートフォンで幅いっぱい、PC では 600px 幅
  • 最初の画面に、メイン画像1枚とサムネイル8枚。下にレビュー画像が数十枚
  • 利用者の7割がスマートフォンで、通信量を気にする層が多い
  • CLS も悪く、画像が届くたびに購入ボタンの位置がずれる
この軸で考える
  • · 転送量・発見の早さ・展開の重さ・場所の確保を、どの工程で解決するか
  • · 画像の役割(主役・最初の画面・画面外)ごとに、指定を変えるか
  • · 出品者がアップロードする大きさを、誰がどこで揃えるか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「画像は全部圧縮したのに、古いスマートフォンで商品ページがカクつく」と言うデザイナーに、何が起きているかを説明してください

与えられた条件
  • 相手は画像の書き出しと圧縮には詳しい
  • アップロードしているのは 4000px の画像で、ファイルは 400KB 程度まで圧縮済み
  • 開発側で何を変えるか、デザイナー側で何を変えるかを分けて伝えたい

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。