【Core Web Vitals】imgタグをレスポンシブにする方法
はじめに
今まで、Webページの画像をレスポンシブ対応させる時は、スマホで見たときに画像がはみ出していないか、縦横比がおかしくなっていないかを主に意識していました。しかし、先日Core Web Vitalsを確認していたところ、「画像の読込時間」という視点が抜けていたことに気が付きました。
もっと言うと、そもそもimgタグで表示した画像のサイズについて勘違いをしていたり、Core Web Vitalsを意識した画像のレスポンシブ対応方法を知らなかったり、 色々課題が見えてきました。
特に、Webページの初期表示速度はユーザビリティに直結するため、Google社も検索ランキングを付ける際に厳しくチェックしています。SEO面でも対策必須になるかと思います。
HTMLは基本エラーを吐かず、なかなか勘違いに気が付きません。私もあまり調べずにここまで来てしまったので、記事にして整理したいと思います。
この記事の内容
Core Web Vitals
画像のレスポンシブ対応を行う際に、重要な指標となる以下の2点の概要を解説します。
- LCP(Largest Contentful paint)
- CLS(Cumulative Layout Shift)
imgタグ
- widthとheight属性の目的
- srcsetとsizes属性を使ったレスポンシブ対応方法
前提知識
HTML,CSSの基礎的な内容を把握している方を想定して記載しています。メディア・クエリを使ったレスポンシブ対応についても触れますが、レスポンシブ対応は初めての方でも特に問題ありません。
HTMLとCSSの基礎的な内容と言っても、ほんの少しの知識がある程度で大丈夫です。
Core Web Vitals
Webサイトの品質を図るために、Google社が提示している指標です。
公式ページでは以下のように概要が説明されています。
Web Vitalsとは
Web Vitals は、Web 上での優れたユーザー エクスペリエンスの提供に欠かすことのできない品質シグナルに関する統一的なガイダンスの提供を目的とした、Google によるイニシアチブです。
Core Web Vitalsとは
Core Web Vitals とは、すべての Web ページに適用可能な Web Vitals のサブセットのことを指します。
現時点のサブセットは3種類あります。今回説明するのは上の2つです。
- LCP: Largest Contentful Paint
- CLS: Cumulative Layout Shift
- FID: First Input Delay
2024.3.12より、FIDはINP(Interaction to Next Paint)に置き換えられます。詳細はNext Paint へのインタラクションを進めるをご確認ください。
LCP(Largest Contentful paint)
日本語では「最大視覚コンテンツの表示時間」と略されています。ページの初期読込の開始から起算して、ビューポート内に表示される最も大きい要素(画像やテキスト・ブロック)のレンダリングにかかる時間のことです。ほとんどのページでは画像が該当するかと思います。
2.5秒以内がGood、4秒以内が要改善、4秒超が酷い(poor)と指標が設定されています。
CLS(Cumulative Layout Shift)
こちらは「累積レイアウトシフト数」と略されています。画像などが表示された時に、まわりのページコンテンツが「どれだけ移動したか」を示す指標です。
公式の動画を見るとイメージが付きやすいので、貼ります。戻るボタンを押そうとした瞬間、レイアウトがシフトして注文確定ボタンを押してしまうという、実害が出てしまうサンプルです。ポインターの動きが茶目っ気があってかわいいですね。でも、似たような経験をされた方は多いのではないでしょうか。
指標の計算方法ですが、かなり難しく私も詳細には理解できていません。GMO社のWEB集客ラボによると、以下のように計算されるとのことです。
CLSスコアは、表示領域(viewport)における「レアウトシフトの影響を受けた面積」×「実際にずれて動いた距離」で算出できます。
シフトするものが複数あれば、これを累積したものがスコアになります。
LCPと同じく、あくまでビューポート内のレイアウトに限ります。ビューポート外でいくらシフトしようとも、指標には影響ありません。また、「意図しないシフト」が対象となり、CSSのtransform等で要素を移動させる場合などはノーカウントになります。
0.1以下がGood、0.25以下が要改善、0.25超が酷い(poor)と指標が設定されています。
確認方法
いずれもGoogle Search ConsoleにWebサイトを登録すれば、診断してくれます。ちなみに、このサイトのランディングページは、デスクトップ表示だとLCPが2.0秒でギリギリ、CLSは0でした。
モバイルだと酷い結果になりましたので、貼りません。少しずつチューニングしていくので許してください。
私のレスポンシブ対応と問題点
例えば横1230px、縦501pxの画像を、以下のようなHTMLとCSSで描写したとします。
HTML
<div class="container">
<img class="responsive" src="sakura.JPG" alt="sakura">
</div>
CSS
.container {
width: 35%;
margin: auto;
text-align: center;
}
.responsive {
width: 100%;
height: auto;
}
@media screen and (max-width:600px) {
.container {
width: 80%;
}
}
imgタグの親要素に可変のwidthを指定し、その幅に画像の横幅を合わせています。そしてメディア・クエリを使って、デバイスの横幅が600px以下の場合に、可変のwidthの比率を上げています。
imgタグにCSSでheight:auto;
を設定することで、元の画像のアスペクト比を保ったまま高さが自動調整されます。
実際にブラウザで表示すると以下のように表示されます。
デバイス幅1024pxの表示例
デバイス幅600pxの表示例
デバイスの幅を狭めると、デバイスの幅に対して画像の横幅の比率が大きくなっているのが確認できます。
もとの画像は横1230px、縦501pxでした。ブラウザに表示された画像の大きさは、デバイス幅1024pxの場合は約横353px、縦144px、デバイス幅600pxの場合は約横321px、縦131pxになっており、リサイズされていることも確認できます。
インターネットで調べると、このやり方を紹介しているサイトも見かけるので、割と一般的な方法だと思います。アスペクトの比率を維持してくれるので、縮尺がおかしくならないので便利です。
しかしこのやり方だと、Core Web Vitalsに照らすと、あまり良くない点が2つあります。
LCPの問題
上記のとおり、元の画像は横1230px、縦501pxでした。しかし、ブラウザに表示されている画像はもっと小さいです。
しかしながら、ブラウザに表示される大きさに関わらず、クライアントに転送される画像サイズは横1230px、縦501pxのものです。 HTMLやCSSの定義に従って、ブラウザが画像をリサイズして表示をしてくれています。
今まで、不要に大きな画像が使われていないか、ブラウザ上の画像サイズで確認していましたが、これは全く意味の無いことでした。基本なのかもしれませんが、私は勘違いしてしまっていました。
冷静に考えれば、1つのサイズの画像をサーバに置いてあるだけなので、クライアントに転送する前に、都合良くサイズダウンしてくれる訳がありませんよね。。。
これは、特にディスプレイが小さい端末では、不要に大きな画像をダウンロードをしてしまうことになります。画像の最適化が行われていないという点で、LCPを不要に悪化させる要因になってしまいます。
CLSの問題
この例では、画像のwidthはビューポートに応じた割合で設定されています。ブラウザはビューポートの幅は取得できるので、画像が転送される前でも、画像のwidthを事前に計算することができます。
heightに関しては、アスペクト比を維持するため、CSSでheight:auto
としています。事前にwidthが分かっていても、元画像のwidthとheightからアスペクト比の計算をしないと、レンダリングすべきheightの計算ができません。つまり、画像の転送が終わらないと、表示すべき高さの計算ができないということになります。
この場合、画像の読込が完了するまで、画像分のスペースを空けずに、次の要素が先にレンダリングされる場合があります。そうすると、画像が表示された時に、height分下の要素がシフトしてしまい、CLSが悪化する原因になってしまいます。
解決策
CLSの解決策
MDNの<img>: 画像埋め込み要素を確認すると、imgタグのwidthとheightについて以下のように解説されています。
width と height の両方を使用して画像の固有の寸法を設定すると、画像を読み込む前に場所を確保し、コンテンツのレイアウトが移動することを防ぐことができます
「固有の寸法」と表現されていますが、原文だとintrinsic size(実際のサイズ)となっています。
実際の画像のサイズを設定することによって、画像の転送が終わる前でもアスペクト比が分かります。そのため、ブラウザが事前に高さを計算して画像分のスペースを空けておいてくれるという訳です。
imgタグのwidthとheightは実際にブラウザに表示したいサイズを設定するものだと思っていました。「それじゃあレスポンシブ対応できないじゃない」と、今までCSSに任せて設定していませんでした。アスペクト比を計算するという役割があったとは、、、恥ずかしながら今まで知りませんでした!
なお、widthとheightは実際の画像サイズを設定し、表示する画像のサイズを調整する必要がある場合はCSSで行う、というのがグッド・プラクティスとのことです。
HTMLはエラーをほとんど吐かないので、こういった情報は調べないと知らないままになってしまうのが怖いですね。
以上を踏まえて、サンプルのHTMLを修正してみます。
HTML
<div class="container">
<!-- 画像の実サイズを設定 -->
<img class="responsive" src="sakura.JPG" alt="sakura" width="1230" height="501">
</div>
CSS
CSSは変更ありません。
.container {
width: 35%;
margin: auto;
text-align: center;
}
.responsive {
width: 100%;
height: auto;
}
@media screen and (max-width:600px) {
.container {
width: 80%;
}
}
参考サイト
LCPの解決策
あらかじめ大きさの異なる画像を複数準備しておき、デバイスのビューポートに応じて表示する画像を切り替える手法があります。そうすることで、ディスプレイの小さいデバイスでPC用の大きな画像がダウンロードされることがなくなり、画像の読込時間を短縮できます。通信量の節約にもなりますね。また、別の画像に切り替えて表示するので、PC用の大きな画像が小さく縮小されて見にくくなることもありません。
これは、imgタグのsrcset属性とsizes属性を使うことで実現できます。従来のCSSで画像サイズを変更するのではなく、HTMLで画像を切り替える手法になります。
なお、ディスプレイ・サイズの異なるデバイスごとに画像を切り替える問題のことを、「アート・ディレクション問題」と呼ぶようです。
srcsetとsizesの使い方
少し例を変えてみてみます。画面の幅が800px以下の場合は、縦横300px・300pxのジョウビタキの写真、800pxを超える場合は縦横499px・500pxのカワセミの写真を表示する場合を考えます。
<div>
<img
srcset="jou-female.jpg 300w,kawasemi.jpg 500w"
sizes="(max-width: 800px) 300px, 500px"
src="kawasemi.jpg" alt="bird" width="500" height="499">
</div>
srcset
候補となる画像の一覧をカンマ区切りで記述します。
「ファイル名」 + 「半角スペース」 + 「実画像の横幅(ピクセル)」 + 「w」で1セットです。
最後に小文字のwが必要です。横幅は表示したい幅ではなく、実際の画像の幅です。jou-female.pngの横幅が300ピクセル、kawasemi.jpgの横幅が500ピクセルのため、上記の記載になっています。
sizes
画面幅に応じた画像の表示領域のサイズをカンマ区切りで指定します。
「(メディア・クエリ)」 + 「半角スペース」 + 「表示領域のサイズ」 + 「pxもしくはvw」
「表示領域のサイズ」で指定したサイズに、もっとも近いサイズの画像をsrcsetから選択してくれます。少しややこしいですが、メディア・クエリを満たした場合に、画像領域として確保する幅サイズを記述します。実画像の横幅ではありません。pxもしくはvwでの指定が可能です。
一番最後には、いずれのメディア・クエリにもマッチしなかった場合の表示領域のサイズを記述します。そのため、一番最後のサイズはメディア・クエリの記述はありません。
なお、最初にマッチしたメディア・クエリに対応するサイズが適用されるので、複数並べる場合は順番に注意する必要があります。これは通常のCSSのメディア・クエリと同じですね。
(max-width: 800px) 300px, 500px
だと、「画面幅800ピクセル以下の場合、表示領域を300px確保。以外は500px確保」となります。
src,width,height,alt
srcsetとsizesを指定しない場合と同様なので、記述したほうが良いようです。
srcは、srcsetとsizesをサポートしていないブラウザの場合や、srcsetから画像の選択が出来ない場合に利用されます。
widthとheightはアスペクト比計算に使われます。
altは言わずもがな、画像が利用できない場合やスクリーンリーダ使用時に使われます。
画面幅820pxの場合の表示例
画面幅が800pxを超えているので、メディア・クエリの条件を満たしません。この場合の画像の表示領域は500pxなので、srcsetの中から500wの画像が選択されています。
画面幅780pxの場合の表示例
メディア・クエリの条件を満たすので、表示領域300pxにもっとも近いサイズの画像が選択されています。
注意点
ブラウザが画像をキャッシュするので、ローカルで確認する際はキャッシュを無効化したほうが良いです。また、ブラウザによって挙動が微妙に異なる場合があります。
別の対応方法
こちらは参考までです。
imgタグのsrcset,sizes属性を使わずに、pictureタグを使うことで同じことが実現できます。
<picture>
<source media="(max-width:800px)" srcset="jou-female.jpg" width="300" height="300">
<img src="kawasemi.jpg" alt="bird" width="500" height="499">
</picture>
いままでimgタグのsrcsetに入れていたものを、sourceタグに分割して入れます。今回はメディア・クエリが1つだけなのでsourceタグも1つですが、複数ある場合はその分sourceタグを追加します。
最後のimgタグは、sourceのいずれにも合致しない場合や、ブラウザがpictureタグをサポートしていない場合に使用されます。省略は出来ず、必須となります。
chromeで試した感じだと、imgタグのsrcset、sizesを使うより素直に画像の切替を行ってくれる印象です。imgタグの場合だと、ビューポートを変更してからページを再度読み込まないと画像が切り替わらなかったりしましたが、pictureタグの場合は再読み込みせずとも切り替えてくれました。
参考サイト
最後に
長くなってしまいましたが、imgタグのsrcsetとsizes属性を使ってディスプレイの異なるデバイスごとに表示する画像を切り替える方法と、imgタグのwidthとheightを指定して、画像読込後のレイアウト・シフトの影響を小さくする方法を紹介しました。
いずれも、ユーザビリティ向上に繋がり、Core Web Vitalsを満たすために重要な手法だと思います。
私も、いきなり全てのページで対応するのは難しいですが、できるところから対応を進めていこうと考えています。特に、Google社はまずモバイルでSEO評価すると言われていますので、だいぶ修正は多くなりますが、少しずつモバイル用の画像を準備していきます。