主なパフォーマンスの問題

現在、画像は、合計転送サイズと 1 ページあたりのリクエスト数の両面でウェブ最大のアセットです。ウェブページの合計転送サイズの中央値は 2022 年 6 月時点で 2 MB ほどで、画像だけでその半分近くを占めています。画像リクエストの最適化が、最大のパフォーマンス最適化であると言っても過言ではありません。

今回は、どのような場合にも 1 つの画像を提供することで生じる問題に対処するために、レスポンシブな画像がどのような進化を遂げたのかを学びます。 このセクションでは、画像に関する主なパフォーマンス指標とその改善方法について説明します。

画像リクエストの延期

これから画像リクエストを可能な限り小さくして効率良くするいくつかの方法を学ぼうとしていますが、最速の画像リクエストが常に実行されることはありません。まず、画像アセットをユーザーに表示する方法に対して、最も影響力のある変更である loading="lazy" 属性についてご説明します。

<img src="image.jpg" loading="lazy" alt="…">

この属性を使用すると、ユーザーのビューポートに近づくまで画像のリクエストは実行されず、最初のページ読み込み(ブラウザが最もビジー状態になる時間)からとらえ、それらのリクエストをクリティカル レンダリング パスから削除します。

この属性を使用すると、実際には単純にパフォーマンスが向上します。ユーザーのビューポート内に収まらない画像はリクエストされず、ユーザーに表示されない画像で帯域幅が無駄になることもありません。

ただし、注意が必要なのは、リクエストを延期すると、ブラウザの最適化されたプロセスを利用してできるだけ早く画像をリクエストしないということです。loading="lazy" がレイアウト上部の img 要素で使用される(つまり、ページが最初に読み込まれたときにユーザーのビューポート内にある可能性が高い)場合、エンドユーザーにとってこれらの画像は著しく遅く感じられる可能性があります。

優先度を取得する

loading 属性は、ウェブブラウザによるリクエストの優先順位付けをデベロッパー自身が管理できるようにするための、ウェブ標準に対する大規模な取り組みの一例です。

ブラウザの優先度を取得する基本的なアプローチについてはご存じのことでしょう。たとえば、ドキュメントの <head> にある外部 CSS ファイルへのリクエストはレンダリングをブロックするのに十分であると考えられ、</body> のすぐ上にある外部 JavaScript ファイルへのリクエストはレンダリングが完了するまで延期されます。<img>loading 属性の値が「lazy」の場合、関連付けられた画像リクエストは、ユーザーに表示されるとブラウザが判断するまで遅延されます。それ以外の場合、その画像の優先度はページ上の他の画像と同じになります。

fetchpriority 属性は、アセットの優先度をきめ細かく制御して、同じタイプのリソースと比較して優先度を「高」または「低」のフラグをデベロッパーに提供するためのものです。fetchpriority のユースケースは loading 属性に似ていますが、はるかに広範です。たとえば、ユーザー操作の後にのみ表示される画像(その画像がユーザーのビューポート内にあるかどうか)に fetchpriority="low" を使用すると、ページの他の場所に表示される画像を優先できます。また、fetchpriority="high" を使用すると、ページがレンダリングされるとすぐにビューポートに表示されることがわかっている画像を優先できます。

fetchpriority は、ブラウザの動作を根本的に変更しないという点で loading とは異なります。ブラウザに対して、特定のアセットを他のアセットより先に読み込むように指示するのではなく、アセットのリクエストに関する判断を下すための重要なコンテキストを提供します。

画像の影響を測定する

画像アセットを最適化する場合、一般に、認識されるパフォーマンスは合計転送サイズのみよりも重要であり、測定が難しくなります。

Web Vitals は、ウェブのユーザー エクスペリエンスを改善するための測定可能で実用的な指標とガイダンスを提供します。ウェブサーバーからの応答時間が遅い問題、レンダリングの問題、インタラクティビティの遅延などの問題がハイライト表示されます。Core Web Vitals はこれらの目標のサブセットであり、ユーザーが個々のページで直接体験できることに焦点を当てています。つまり、一連の技術的な測定値を総合して、ユーザーがどのくらいの速さを感じているかを判断します。

Cumulative Layout Shift

Cumulative Layout Shift(CLS)は、視覚的な安定性の尺度です。これは、アセットが読み込まれてページがレンダリングされるときに、ページのコンテンツのレイアウトがどの程度変化するかをキャプチャするための指標です。かなりの時間を費やしてウェブを使用している人は、遅延のウェブフォントや画像ソースが突然レンダリングされたり、インタラクティブ要素がポインタから突然離れたりしてページが「ジャンプ」したりして、長時間にわたってテキストの場所を失ってしまいます。CLS の値が大きいと、せいぜい煩わしいものとなり、最悪の場合はユーザーエラーの原因となります。たとえば、ユーザーがクリックしたとき、そのとき「確認」ボタンが占めていたスペースに「キャンセル」ボタンが移動するからです。

読み込み時間が長く、レイアウトで占有できるスペースが大きいことから、CLS スコアが高い主な原因として画像が挙げられます。

最新のブラウザにおける比較的最近の変更のおかげで、画像に起因する高い CLS スコアを避けるのは思ったよりも容易です。

数年前からフロントエンドに携わっている方は、<img>width 属性と height 属性にはおなじみのことでしょう。CSS が広く採用される以前は、画像のサイズを制御する唯一の方法はこれらでした。

<img src="image.jpg" height="200" width="400" alt="…">

これらの属性は、スタイル設定に関する問題をマークアップから切り離すために使用されなくなりました。特にレスポンシブ ウェブ デザインでは、CSS を使用して割合ベースのサイズを指定する必要があるためです。レスポンシブ ウェブ デザインの初期の頃は、「未使用の width 属性と height 属性を削除する」が一般的でした。これは、CSS で指定した値(max-width: 100%height: auto)がオーバーライドするためでした。

<img src="image.jpg" alt="…">
img {
  max-width: 100%;
  height: auto;
}

前の例のように height 属性と width 属性を削除しました。この状況で画像の高さを判断するためのブラウザが持つ唯一の方法は、ソースをリクエストして解析し、スタイルシートが適用されるとレイアウト内で占有するスペースの幅に基づいて、固有のアスペクト比でレンダリングすることです。この処理の多くはページのレンダリング後に行われ、新しく計算された高さによって追加のレイアウト シフトが発生します。

2019 年以降width 属性と height 属性の処理方法が異なるようにブラウザの動作が更新されました。これらの属性の値を使用してレイアウト内の img 要素のピクセルベースの固定サイズを決定するのではなく、構文は同じですが、これらの属性は画像のアスペクト比を表すと考えることができます。最新のブラウザでは、ページをレンダリングする前に img 要素に固有のアスペクト比を決定するために、これらの値を相互に分割します。これにより、レイアウトのレンダリング時に画像が占有するスペースを予約できます。

原則として、<img> では必ず height 属性と width 属性を使用し、画像ソースの本来のサイズと一致する値を使用してください。ただし、height: automax-width: 100% とともに指定して、HTML 属性の高さをオーバーライドするようにしてください。

<img src="image.jpg" height="200" width="400" alt="…">
img {
  max-width: 100%;
  height: auto;
}

<img> 要素で width 属性と height 属性を使用すると、画像が原因で CLS スコアが高くなることを回避できます。

このアプローチには欠点がないことに注意してください。これは、長年のブラウザの動作に依存しているためです。基本的な CSS をサポートしているブラウザであれば、マークアップの height 属性と width 属性がスタイルによってオーバーライドされるため、常に同じように機能します。

width 属性と height 属性を使用すると、画像に必要なレイアウト スペースを確保することで CLS の問題を効果的に回避できますが、画像の転送とレンダリングを待機している間に、ユーザーに空白のギャップまたは低品質のプレースホルダを表示するのも理想的ではありません。読み込みが遅い画像による測定可能で認識可能な影響を軽減する方法はありますが、完全にレンダリングされた画像をユーザーに迅速に表示するための唯一の方法は、転送サイズを小さくすることです。

Largest Contentful Paint

Largest Contentful Paint(LCP)は、ユーザーのビューポートに表示される最大の「コンテンツの多い」要素(表示されるページの最大の割合を占めるコンテンツ要素)をレンダリングするまでにかかった時間を測定します。表面的には過度に具体的な指標に見えるかもしれませんが、この要素は、ユーザーの視点から、ページの大部分がレンダリングされたポイントを表す実用的な代替要素として機能します。LCP は(認識される)パフォーマンスの重要な尺度です。

DOMContentLoadedwindow.onload イベントなどの指標は、現在のページの読み込みプロセスが技術的に完了したタイミングを判断するのに役立ちますが、必ずしもユーザーのページ エクスペリエンスに対応しているわけではありません。ユーザーのビューポートの外側にある要素のレンダリングにわずかな遅延があることは、これらの指標のいずれかで考慮されますが、実際のユーザーではまったく検出されない可能性があります。LCP が長いということは、ページのユーザーの第一印象(現在のビューポート内の最も重要なコンテンツ)で、ページの動作が遅いか、まったく読みにくい状態にあることを意味します。

LCP が把握するユーザーの認識は、ユーザー エクスペリエンスに直接影響します。昨年 Vodafone が実施したテストでは、LCP が 31% 改善すると、売り上げが 8% 増加しただけでなく、ユーザーの総数に対しても売上が 8% 増加しただけでなく、見込み顧客となる訪問者の数(「訪問からリード率」)が 15% 改善し、カートを訪れたユーザー数(「カート訪問率」)が 11% 改善しました。

ウェブページの 70% 以上で、最初のビューポートの最大の要素に、単独の <img> 要素または背景画像を含む要素として画像が含まれています。つまり、ページの LCP スコアの 70% は画像のパフォーマンスに基づきます。なぜかわからない場合は、大きな関心を引くような画像やロゴが「スクロールせずに見える範囲」に表示される可能性が高くなります。

web.dev ページのコンソールで LCP がハイライト表示されている

LCP の遅延を回避するには、いくつかの方法があります。まず、「スクロールせずに見える範囲」の画像に loading="lazy" を指定しないでください。ページがレンダリングされるまでリクエストを遅らせると、LCP スコアに悪影響が及ぶ可能性が高くなります。次に、fetchpriority="high" を使用すると、この画像の転送をページ上の他の画像より優先する必要があることをブラウザに伝えることができます。

これらのルールを念頭に置いて、ページの LCP スコアを改善するために最も重要なことは、画像の転送とレンダリングにかかる時間を減らすことです。そのためには、画像ソースを(品質を犠牲にすることなく)できる限り小さくして、効率化し、ユーザーが閲覧する状況に最適な画像アセットのみを取得できるようにする必要があります。

まとめ

画像アセットはユーザーの帯域幅を大量に消費するため、ページのレンダリングに必要な他のすべてのアセットを転送するために必要な帯域幅が必要になります。画像は、周囲のページ レイアウトがレンダリングされる最中と後で、認識されるパフォーマンスの点で重大な問題をもたらします。つまり、画像アセットには損害が生じます。

難しすぎるかもしれませんが、「画像が少ない方がウェブに良い」というのは、パフォーマンスのみという点では確かに言えることですが、ユーザーにとっては、大きな悪影響も生じます。画像はウェブに不可欠な要素です。パフォーマンスのためだけに、意味のあるコンテンツの品質について妥協すべきではありません。

このコースの残りの部分では、画像アセットに活用されているテクノロジーと、品質を損なうことなくパフォーマンスへの影響を軽減する手法について説明します。