記述構文

このモジュールでは、表示する画像を適切に判断できるように、ブラウザが画像を選択できるようにする方法を学習します。srcset は、特定のブレークポイントで画像ソースを入れ替えるメソッドではなく、画像を別の画像に入れ替えるものではありません。これらの構文により、ブラウザはユーザーのブラウジング コンテキスト(ビューポートのサイズ、表示密度、ユーザー設定、帯域幅、その他の無数の要素など)に応じて調整された画像ソースをシームレスにリクエストしてレンダリングする、非常に困難な問題を解決できます。

これは大きな要求です。ウェブ用に画像をマークアップしているだけの場合、当然ながら検討する範囲を超えます。適切にマークアップするには、利用できる情報量を超える情報が必要になります。

x で密度を記述する

固定幅の <img> は、ユーザーのディスプレイの密度(画面を構成する物理ピクセルの数)に関係なく、すべてのブラウジング コンテキストでビューポートと同じ量を占有します。たとえば、固有の幅 400px の画像は、初代 Google Pixel と新しい Google Pixel 6 Pro の両方でブラウザのビューポートのほぼ全体を占めます。どちらのデバイスも、正規化された 412px 論理ピクセルの幅のビューポートを備えています。

Google Pixel 6 Pro のディスプレイははるかに鮮明ですが、Google Pixel 6 Pro の物理解像度は 1,440 x 3,120 ピクセル、Google Pixel は 1,080 x 1,920 ピクセル(画面を構成するハードウェア ピクセル数)です。

デバイスの論理ピクセルと物理ピクセルの比率は、そのディスプレイのデバイス ピクセル比(DPR)です。DPR は、デバイスの実際の画面解像度をビューポートの CSS ピクセルで割って計算されます。

コンソール ウィンドウに表示された DPR 2。

つまり、初代の Google Pixel の DPR は 2.6 ですが、Google Pixel 6 Pro の DPR は 3.5 です。

DPR が 1 を超える最初のデバイスである iPhone 4 は、デバイス ピクセル比 2 を報告します。つまり、画面の物理解像度は論理解像度の 2 倍です。iPhone 4 より前のデバイスは、DPR が 1: 1 つの論理ピクセルと 1 つの物理ピクセルでした。

DPR が 2 のディスプレイで 400px 幅の画像を表示すると、各論理ピクセルはディスプレイの 4 つの物理ピクセル(水平方向 2 つ、垂直 2 つ)にわたってレンダリングされます。この画像は、高密度ディスプレイのメリットはありません。DPR が 1 のディスプレイと同じように見えます。もちろん、ブラウザのレンダリング エンジンによって「描画」されるもの(テキスト、CSS シェイプ、SVG など)はすべて、高密度表示に合わせて描画されます。ただし、画像形式と圧縮で説明したように、ラスター画像は一定のピクセル グリッドです。高密度ディスプレイに合わせてアップスケールされたラスター画像は、常にはっきり見えるとは限りませんが、前のページに比べて解像度が低く見えます。

このアップスケーリングを防ぐため、レンダリングされる画像の固有の幅は少なくとも 800 ピクセルである必要があります。400 論理ピクセル幅のレイアウトのスペースに合わせて縮小すると、この 800 ピクセルの画像ソースのピクセル密度は 2 倍になり、DPR が 2 のディスプレイでは美しく鮮明になります。

密度のばらつきを示す花びらのクローズアップ。

DPR が 1 のディスプレイは、画像の高密度を利用できないため、ディスプレイに合わせて縮小されます。ご存じのとおり、縮小された画像は問題なく表示されます。低密度ディスプレイでは、高密度ディスプレイに適した画像は、他の低密度画像と同様に表示されます。

画像とパフォーマンスで説明したように、低密度ディスプレイで 400px にスケールダウンされた画像ソースを表示する場合、必要となるのは幅が 400px のソースのみです。はるかに大きな画像であれば、すべてのユーザーに対して同じように機能しますが、小さな低密度ディスプレイでレンダリングされた巨大で高解像度の画像ソースは、他の小さな低密度画像と同じように見えますが、はるかに遅く感じられます。

ご想像のとおり、DPR が 1 のモバイル デバイスは極めて希少ですが、「パソコン」のブラウジング環境では依然として一般的です。Matt Hobbs が共有したデータによると、2022 年 11 月以降の GOV.UK の閲覧セッションの約 18% は DPR が 1 であると報告しています。高密度の画像はユーザーの期待どおりに見えますが、帯域幅と処理コストは大幅に高くなります。これは、ディスプレイが低密度である可能性が高い旧式で性能の低いデバイスを使用するユーザーにとって特に懸念されます。

srcset を使用すると、高解像度ディスプレイを備えたデバイスのみが、鮮明に見えるのに十分な大きさの画像ソースを受け取るようになり、低解像度ディスプレイのユーザーに同じ帯域幅コストが渡されることはありません。

srcset 属性は、画像をレンダリングする 1 つ以上の候補をカンマ区切りで指定します。各候補は、src で使用するような URL と、その画像ソースを記述する構文の 2 つで構成されます。srcset の各候補は、その固有の幅(「w 構文」)または目的の密度(「x 構文」)によって記述されます。

x 構文は「このソースは、この密度のディスプレイに適切です」の省略形です。DPR が 2 のディスプレイには、候補の後に 2x を付けるのが適切です。

<img src="low-density.jpg" srcset="double-density.jpg 2x" alt="...">

srcset をサポートするブラウザには、2 つの候補が表示されます。double-density.jpg は、DPR が 2 のディスプレイの場合に 2x が適宜記述します。low-density.jpg は、src 属性で適切なものが見つからなかった場合に選択される候補です。srcsetsrcset をサポートしていないブラウザの場合、属性とそのコンテンツは無視されます。つまり、src のコンテンツは通常どおりリクエストされます。

srcset 属性で指定した値は、指示と間違えやすくなります。この 2x は、関連付けられたソースファイルが DPR 2(ソース自体に関する情報)のディスプレイでの使用に適していることをブラウザに通知します。ソースの使用方法をブラウザに指示するものではなく、ソースの使用方法をブラウザに伝えるだけです。これは微妙ながらも重要な違いです。倍密度の画像であり、倍密度ディスプレイに使用する画像ではありません。

「このソースは 2x ディスプレイに適切です」という構文と、「このソースを 2x ディスプレイで使用する」という構文の違いはわずかですが、表示密度は、ブラウザがレンダリング候補の決定に使用する膨大な数の相互リンクされた要因の一つにすぎず、わかるのはほんの一部です。たとえば、個別に、prefers-reduced-data メディアクエリを介してユーザーが帯域幅節約のブラウザ設定を有効にしていることを確認し、それを使用して、表示密度に関係なく常にユーザーに低密度画像を表示するようにできます。ただし、すべてのデベロッパーがすべてのウェブサイトで一貫して実装しない限り、ユーザーにとってあまり役に立たないでしょう。あるサイトでは自分の好みを尊重し、次のサイトでは帯域幅を浪費する画像ばかりになることがあります。

srcset/sizes で使用される意図的にあいまいなリソース選択アルゴリズムにより、ブラウザは帯域幅がディップする低密度の画像を選択したり、データ使用量を最小限に抑えることを優先して選択したりする余地を残しています。この場合、いつ、どのようなしきい値にするかについて Google は責任を負いません。ブラウザの方がもっと適した処理を担うという意味ではありません。責任を負い、別の作業も負うことに意味はありません。

w で幅を記述する

srcset は、イメージソース候補の 2 つ目のタイプの記述子を受け入れます。はるかに強力であり、私たちにとっては理解しやすい非常に便利です。w 構文では、特定の表示密度に適した寸法を持つ候補にフラグを立てるのではなく、各候補ソース固有の幅を記述します。ここでも、各候補は同じサイズ(同じコンテンツ、同じ切り抜き、同じアスペクト比)以外は同一です。しかし、この場合、ユーザーのブラウザで 2 つの候補から選択する必要があります。small.jpg はソース固有の幅が 600px で、large.jpg はソース固有の幅が 1,200px です。

srcset="small.jpg 600w, large.jpg 1200w"

これは、この情報の用途をブラウザに指示するものではなく、画像を表示する候補のリストを提供するだけです。ブラウザがレンダリングするソースを決定できるようにするには、ページ上での画像をレンダリングする方法の説明という、もう少し詳しい情報を提供する必要があります。そのためには、sizes 属性を使用します。

sizes で使用状況を説明する

ブラウザは、画像の転送という点で驚くほど高性能です。画像アセットのリクエストは、スタイルシートや JavaScript のリクエストよりもかなり前に開始されます(多くの場合、マークアップが完全に解析される前であっても)。ブラウザがこれらのリクエストを行うとき、マークアップ以外のページ自体に関する情報はありません。外部スタイルシートのリクエストを開始しておらず、そのリクエストを適用していない可能性もあります。ブラウザがマークアップを解析し、外部リクエストを開始する時点では、ブラウザにはブラウザレベルの情報(ユーザーのビューポートのサイズ、ユーザーのディスプレイのピクセル密度、ユーザーの設定など)しかありません。

この場合、ページ レイアウトでの画像がどのようにレンダリングされるかはわかりません。水平方向にスクロールするコンテナを占有する可能性があるため、ビューポートを img サイズの上限のプロキシとして使用することはできません。したがって、この情報をブラウザに提供し、マークアップを使用して処理する必要があります。これらのリクエストでご利用いただけるのは以上です。

srcset と同様に、sizes は、マークアップが解析されるとすぐに画像に関する情報を利用できるようにすることを目的としています。srcset 属性が「ここにソースファイルとその固有のサイズがあります」の省略形であるのと同様に、sizes 属性は「レイアウト内のレンダリングされた画像のサイズ」の省略形です。画像の説明はビューポートを基準としたものになりますが、画像のリクエスト時にブラウザが持つレイアウト情報はビューポートのサイズだけです。

印刷では少し複雑に思えるかもしれませんが、実際には理解しやすいでしょう。

<img
 sizes="80vw"
 srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
 src="fallback.jpg"
 alt="...">

ここで、この sizes 値により、img が占有するレイアウト内のスペースの幅が 80vw(ビューポートの 80%)であることをブラウザに通知します。これは命令ではなく、ページ レイアウトの画像サイズの説明であることに注意してください。「この画像がビューポートの 80% を占めるようにする」というメッセージはありませんが、「ページがレンダリングされると、この画像がビューポートの 80% を占有します」というメッセージはありません。

デベロッパーとしての仕事は完了です。srcset で候補ソースのリストを、sizes で画像の幅を正確に記述しました。srcsetx 構文と同様に、後はブラウザ次第です。

しかし、この情報がどのように使用されるかを完全に理解するために、ユーザーのブラウザがこのマークアップを目にしたときに下される決定について、少し時間を取って見てみましょう。

この画像が利用可能なビューポートの 80% を占めることをブラウザに通知しました。したがって、幅が 1,000 ピクセルのビューポートのデバイスでこの img をレンダリングすると、この画像は 800 ピクセルを占有します。ブラウザはその値を取得し、srcset で指定した各画像ソース候補の幅で除算します。最小のソースの固有サイズは 600 ピクセルであるため、600÷800=0.75 となります。中程度の画像は幅 1,200 ピクセル(1,200÷800=1.5)です。最も大きな画像は幅 2,000 ピクセル、2,000÷800=2.5 です。

これらの計算結果(.751.52.5)は、実質的に、ユーザーのビューポート サイズに合わせて調整された DPR オプションになります。ブラウザには、現在のユーザーの表示密度に関する情報も含まれているため、次のような決定を行います。

このビューポート サイズでは、small.jpg 候補はユーザーの表示密度に関係なく破棄されます。計算された DPR が 1 より低い場合、このソースはすべてのユーザーのアップスケーリングを必要とするため、適切ではありません。DPR が 1 のデバイスでは、medium.jpg が最も近い一致となります。このソースは DPR が 1.5 で表示されるのに適しているため、必要以上に大きくなりますが、ダウンスケーリングは視覚的にシームレスなプロセスであることに留意してください。DPR が 2 のデバイスの場合、最も近い値は large.jpg であるため、これが選択されます。

同じ画像を 600 ピクセル幅のビューポートにレンダリングすると、80vw は 480px になるため、まったく異なる計算結果となります。これに対してソースの幅を除算すると、1.252.54.1666666667 になります。このビューポート サイズでは、1x のデバイスでは small.jpg が選択され、2x のデバイスでは medium.jpg が一致します。

この画像は、これらすべてのブラウジング コンテキストで同じように見えます。すべてのソースファイルはサイズから離れてまったく同じであり、それぞれがユーザーの表示密度で許容される限り鮮明にレンダリングされます。ただし、最大のビューポートと最高密度のディスプレイに対応するためにすべてのユーザーに large.jpg を配信するのではなく、常に最適な最小の候補がユーザーに表示されます。規定の構文ではなく記述的な構文を使用することで、ブレークポイントを手動で設定したり、将来のビューポートや DPR について考えたりする必要がなくなります。ブラウザに情報を渡すと、ブラウザが答えを判断できるようになります。

sizes の値はビューポートを基準としており、ページ レイアウトから完全に独立しているため、ウォッチフェイスの追加機能のレイヤが追加されます。余白やパディングなどがなく、ページ上の他の要素から影響を受けずに、ビューポートの割合のみを占める画像を使用することはまれです。多くの場合、画像の幅は、パーセント、empx などの単位を組み合わせて表現する必要があります。

幸いなことに、ここでは calc() を使用できます。レスポンシブな画像をネイティブにサポートしているブラウザであれば calc() もサポートするため、CSS ユニットを組み合わせることができます。たとえば、ユーザーのビューポートの全幅を占有し、両側に 1em の余白がない画像などです。

<img
    sizes="calc(100vw-2em)"
    srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w, x-large.jpg 2400w"
    src="fallback.jpg"
    alt="...">

ブレークポイントの説明

レスポンシブ レイアウトに多くの時間を費やしてきたなら、以下の例にはない部分に気づくはずです。たとえば、レイアウト内で画像が占有するスペースは、レイアウトのブレークポイントで変化する可能性が非常に高いです。その場合は、もう少し詳細な情報をブラウザに渡す必要があります。sizes は、srcset が画像ソースのカンマ区切りの候補を受け入れるのと同様に、画像のレンダリング サイズの候補のカンマ区切りのセットを受け入れます。これらの条件では、一般的なメディアクエリ構文を使用します。この構文は最初に一致します。メディアの条件が一致すると、ブラウザは sizes 属性の解析を停止し、指定された値が適用されます。

1,200 ピクセルを超えるビューポートで、ビューポートの 80% を占有し、左右のパディング em を引いた画像があるとします。つまり、1,200 ピクセルを超えるビューポートでは、その画像がビューポートの幅いっぱいに表示されます。

  <img
     sizes="(min-width: 1200px) calc(80vw - 2em), 100vw"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

ユーザーのビューポートが 1, 200 ピクセルを超える場合、calc(80vw - 2em) はレイアウト内の画像の幅を表します。(min-width: 1200px) 条件が一致しない場合、ブラウザは次の値に進みます。この値には特定のメディア条件が関連付けられていないため、デフォルトとして 100vw が使用されます。max-width メディアクエリを使用してこの sizes 属性を記述すると、次のようになります。

  <img
     sizes="(max-width: 1200px) 100vw, calc(80vw - 2em)"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

(max-width: 1200px) は一致しますか?」それ以外の場合は、次の手順に進みます。次の値(calc(80vw - 2em))には適格条件がないため、これが選択されます。

img 要素に関するすべての情報(考えられるソース、固有の幅、ユーザーへの画像の表示方法)をブラウザに提供したので、ブラウザはその情報の処理方法を決定するためにファジーなルールセットを使用します。曖昧に思えるかもしれませんが、それは意図的なためです。HTML 仕様にエンコードされているソース選択アルゴリズムは、ソースの選択方法が明確にあいまいになっています。ソース、その記述子、画像のレンダリング方法がすべて解析されると、ブラウザは自由に任意の処理を実行できます。ブラウザがどのソースを選択するかはわかりません

「このソースを高解像度ディスプレイで使用する」という構文は予測可能ですが、レスポンシブ レイアウトの画像に関する主要な問題、つまりユーザーの帯域幅を節約することには対応できません。画面のピクセル密度は、インターネット接続速度にのみ関係します。最先端のノートパソコンを使用しているが、従量制接続、スマートフォンへのテザリング、不安定な飛行機 Wi-Fi 接続でウェブをブラウジングしている場合は、ディスプレイの品質に関係なく、高解像度の画像ソースをオプトアウトできます。

最後の決定をブラウザに任せることで、厳密な規範的な構文で行うよりもはるかにパフォーマンスを向上できます。たとえば、ほとんどのブラウザでは、srcset または sizes 構文を使用する img は、ユーザーがブラウザのキャッシュにすでに保持しているディメンションよりも小さいディメンションのソースをリクエストすることはありません。ブラウザがすでに所有している画像ソースをシームレスにダウンスケールできる場合、同一に見えるソースを新たにリクエストすることの意味は何ですか。ただし、ユーザーがアップスケーリングを避けるために新しい画像が必要になるまでビューポートを拡大した場合は、そのリクエストは引き続き行われるため、すべてが想定どおりに表示されます。

明示的な制御がないことは、額面では少し恐怖に思えるかもしれませんが、同じコンテンツのソースファイルを使用しているため、ブラウザの決定に関係なく、単一ソースの src ほど「不完全な」エクスペリエンスをユーザーに提供する可能性は低くなります。

sizessrcset の使用

これは、ニュース メディアにとっても、読者にとっても、ブラウザにとっても、多くの情報をもたらします。srcsetsizes はどちらも高密度の構文であり、比較的少ない文字で驚くほど大量の情報を記述します。つまり、設計上、これらの構文を簡潔にし、人間が解析しやすくすることで、ブラウザによる解析がより困難になる可能性があるということです。文字列が複雑になるほど、パーサーエラーや、ブラウザ間での動作の意図しない違いが生じる可能性が高くなります。ただし、この方法には利点があります。マシンでは構文を読みやすくすることで、構文の作成が容易になります。

srcset は自動化に適したケースです。本番環境用に複数のバージョンの画像を手作業で作成するのではなく、Gulp などのタスクランナー、Webpack などのバンドラ、Cloudinary などのサードパーティの CDN、または選択した CMS にすでに組み込まれている機能を使用してプロセスを自動化することはめったにありません。そもそもソースを生成するのに十分な情報があれば、システムは、実行可能な srcset 属性に書き込むのに十分な情報を持つことになります。

sizes は自動化がやや困難です。ご存じのとおり、システムがレンダリングされたレイアウト内の画像のサイズを計算できる唯一の方法は、レイアウトをレンダリングすることです。幸いなことに、sizes 属性を手書きするプロセスを抽象化し、手作業では対応できない効率性を実現するデベロッパー ツールが多数登場しました。たとえば、respImageLint は、sizes 属性の正確性を検証し、改善案を提示するコード スニペットです。Lazysizes プロジェクトでは、レイアウトが確立されるまで画像のリクエストを保留し、JavaScript が sizes 値を生成できるようにすることで、効率がある程度低下します。React や Vue など、完全にクライアントサイドのレンダリング フレームワークを使用している場合、srcset 属性と sizes 属性を作成または生成するためのソリューションがいくつかあります。詳しくは、CMS とフレームワークをご覧ください。