Open7

Web における画像ロードベストプラクティス

@1000ch@1000ch

導入として、Web における画像のペイロードは大きくなる一方で、Core Web Vitals にも大きく影響している。

  • Largest Contentful Paint のために、<link rel="preload">fetchpriority 属性でヒーロー画像のリクエストを早めたり、srcset 属性や <source> 要素で適切なフォーマットの画像をロードし、画面外の画像は loading 属性でロードを遅らせる
  • Cumulative Layout Shift のために、画像の縦横サイズを widthheight 属性で指定したり、CSS の aspect-ratio プロパティで画角を指定する
  • First Input Delay のために、クリティカルレンダリングパスに含まれるリソースのロードを邪魔する画像を使わない

Google 検索へのページ エクスペリエンスの導入時期 の通り、Core Web Vitals の評価は Google 検索ランクにも影響する。

計測には Lighthouse を使う。Chrome の DevTools にもバンドルされている他、Page Speed InsightsWebPageTestCI からでも利用できる。

@1000ch@1000ch

Cumulative Layout Shift

<img> 要素の基本として、 src 属性でファイルの URL を指定するだけでなく、 alt 属性で代替テキストを、 width 属性と height 属性で縦横サイズを指定することでレイアウトを減らす。

<img
  src="keyboard.jpg"
  alt="A beautiful pink keyboard."
  width="400"
  height="400">

width 属性と height 属性を指定することでページレイアウトに占めるサイズが固定され、レイアウトシフトを軽減できる。その重要性については Setting Height And Width On Images Is Important Again でも言及されている。

@1000ch@1000ch

Largest Contentful Paint

画像は Web ページにおける大部分を占める傾向にあり、LCP に該当する場合も多い。よって、ImageOptimSquoosh で最適化しペイロードサイズを減らすだけでなく、適切な画像を <img> 要素でロードする工夫を凝らす必要がある。

<img
  src="keyboard-800w.jpg"
  alt="A beautiful pink keyboard."
  width="400"
  height="400"
  srcset="
    keyboard-400w.jpg 400w,
    keyboard-800w.jpg 800w"
  sizes="(max-width: 640px) 400px, 800px">

srcset 属性によって、ビューポートのサイズやピクセル密度に応じてロードするファイルを指定する。この例では、400px までは keyboard-400w.jpg を、800px までは keyboard-800w.jpg を、それ以外は src 属性で指定されている keyboard-800w.jpg をロードする。

sizes 属性によって、メディアクエリに応じた画像のサイズを指定する。この例では、ビューポートのサイズが 640px までは 400px で、640px より大きい場合は 800px で表示される。

また、昨今ではより圧縮効率の良い WebPAVIF といった新しい画像フォーマットもあるが、<picture> 要素と <source> 要素はそれらを選択的にロードすることを実現する。

<picture>
  <source srcset="keyboard.avif" type="image/avif">
  <source srcset="keyboard.webp" type="image/webp">
  <source srcset="keyboard.jpg" type="image/jpeg">
  <img src="keyboard.jpg" alt="Omg a keyboard">
</picture>

<source> 要素で指定された画像のうちどれをロードするかは、ブラウザの HTTP Accept ヘッダ に依存する。

Web ページの HTML ドキュメントが返却されてパースしている際に、リソースを優先的にロードしたい場合には <link rel="preload"> を使う。LCP が画像の場合などでロードを優先させたい場合は、<link rel="preload" as="image" href="keyboard.webp" type="image/webp"> のように指定することで優先的にリクエストを促すことができる。また、imagesrcset 属性や imagesizes 属性を使うことで、レスポンシブな指定もできる。

<link
  rel="preload"
  as="image" 
  href="keyboard.jpg" 
  imagesrcset="
    poster_400px.jpg 400w, 
    poster_800px.jpg 800w, 
    poster_1600px.jpg 1600w" 
  imagesizes="50vw">

認知的なパフォーマンス改善として、画像がロードされるまでのプレースホルダとして軽量な代替画像を <img> 要素の背景画像として指定しておく手法がある。ここで decoding 属性async を指定しておくことで、ブラウザのメインスレッドを専有しないよう画像のデコード処理を非同期に実行させる。そして非同期に実行している間、プレースホルダとして指定した背景画像が表示される。

<img
  src="donut-800w.jpg"
  alt="A delicious donut"
  width="400"
  height="400"
  srcset="
    donut-400w.jpg 400w,
    donut-800w.jpg 800w"
  sizes="(max-width: 640px) 400px, 800px"
  loading="lazy"
  decoding="async"
  style="
    background-size: cover;
    background-image: url(data:image/svg+xml;base64,[svg text]);">
@1000ch@1000ch

First Input Delay

クリティカルレンダリングパスが処理されてようやくブラウザがアイドル状態になるため、ペイロードサイズの大きい画像も必然的に大きく影響してくる。通常、画像は積極的にダウンロードされるためページ全体の画像をダウンロードするコストも小さくない。ここで loading 属性を使うことで、ブラウザネイティブの遅延ロードを促すことができる。

<img
  src="donut.jpg"
  alt="A delicious pink donut."
  loading="lazy"
  width="400"
  height="400">

loading 属性に lazy が指定されると、スクリーン外に配置されている画像をロードしなくなる。これによってブラウザは初期表示に必要な画像のロードに集中できるため、クリティカルレンダリングパスの構築も阻害しない。また、これは srcset 属性が指定されていても同等に機能する。

@1000ch@1000ch

ここまでは <img> 要素に着目した話だったが、Web ページに含まれるサブリソースは画像だけではない。外部リソースを要求する HTML 要素は <img><iframe><link><script> 要素があり、これらを総合した上でどのような優先度でリソースを要求するかの優先度を指定するのが Priority Hints である。

ここで表現している「リソース」は HTML 上で指定するものに限らず、Fetch Standard で整理される Web におけるリソースに該当するものである。つまり fetch() などにも Priority Hints を指定するオプションが存在するが、この記事の說明においては割愛する。

@1000ch@1000ch

リソースの投機的なロードという需要に対しては、Resource Hints が仕様として存在するが、Priority Hints はそれを補強することになる。先の HTML 要素に対して fetchpriority 属性を指定することで、どのリソースが優先的にロードされるかを示唆する。

例えば、クリティカルレンダリングパスに含まれる <head> 要素内の <link rel="stylesheet"> などは、暗黙的に最も高い優先度でリクエストされるが、リソースを取得する <link> 要素、<script> 要素、<img> 要素、<iframe> 要素は、fetchpriority 属性で互いの優先度関係を明示できる。つまり、<link rel="preload" fetchpriority="low"><script defer fetchpriority="high"> のような指定も必要に応じて可能だ。