ウェブサイトのパフォーマンスを計測をすると、多くのケースでは画像の最適化についての問題が見つかりますが、効果的な改善策の 1 つとして考えられるのが、状況に応じて画像を出し分けるレスポンシブイメージの実装です。

すでに 10 年以上の歴史を持つレスポンシブイメージですが、大きく以下のパターンに分類できます。この記事では、このパターンごとに、実装方法や問題点、今後期待される機能を紹介します。

  1. ビューポートベース
  2. DPR ベース
  3. アートディレクション
  4. 画像フォーマットベース

1. ビューポートベース

見出し「1. ビューポートベース」

ビューポートベースでは、srcset 属性と sizes 属性を使用して、候補となる画像を指定します。特徴としては以下が挙げられます。

  • srcset 属性には、候補となるサイズ違いの画像を列挙する(画角やアスペクト比は同じ)
  • sizes 属性には、画像が表示されるレイアウト幅の条件を明示する
  • 画像選択の決定権はブラウザ側にある
ビューポートベースの図。`srcset` 属性にサイズ違いの画像のリストを指定し、`sizes` 属性に指定した条件に応じて画像が選択される
Photo by Phil S

レスポンシブウェブデザインにおける画像のファイルサイズの問題

見出し「レスポンシブウェブデザインにおける画像のファイルサイズの問題」

レスポンシブウェブデザイン(RWD)では、まず、<img> 要素に以下の CSS を指定することで、レイアウト幅に合わせて画像が縮小します。

img {
  max-width: 100%;
  height: auto;
}

論理プロパティであれば、以下のように指定します。

img {
  max-inline-size: 100%;
  block-size: auto;
}

しかし、<img> 要素の src 属性には、1 つのファイルしか指定できないため、想定されるレイアウト幅の最大サイズの高解像度な画像を指定することになります。

<img src="photo.jpg" alt="代替テキスト" width="2400" height="1800">

この場合、モバイルの小さなスクリーンや、高解像度ディスプレイを搭載していないデスクトップ環境では、必要以上に大きなサイズの画像を読み込むことになります。

特にモバイルでは通信速度が十分ではない場面も多いため、ダウンロードするファイルサイズが大きいとパフォーマンス上のボトルネックになり、通信量の制限にも悪影響を及ぼします。

srcset 属性と sizes 属性

この見出しのリンク

この問題を解消するために、srcset 属性と sizes 属性を使用します。

ビューポートベースのレスポンシブイメージの例
<div class="column">
  <img
    src="photo-small.jpg"
    srcset="
      photo-small.jpg 400w,
      photo-medium.jpg 800w,
      photo-large.jpg 1200w
    "
    sizes="50vw"
    alt="代替テキスト"
    width="400"
    height="400"
  >
</div>

<style>
.column {
  width: 50vw;
}
</style>

srcset 属性で候補となる画像ファイルのパスをカンマ区切りで列挙します。ここで指定する各画像は、画角やアスペクト比が同じでなければなりません。なお、src 属性に指定した画像はフォールバックとして使われます。

ファイル名の後ろに指定されている 400w800w は、ディスクリプタ(Descriptor、記述子)と呼ばれ、w は画像の横幅(width)を意味します。

sizes 属性は、画像が表示されるレイアウト幅を明示します。単位の vw はビューポート幅(viewport width)の割合を意味します。ここでは 50vw と指定しているので、画像がビューポート幅の 50% で表示されることを想定しています。

例えば、ビューポート幅が 1000px のケースで、候補画像との比率を考えてみます。50vw なのでレイアウト幅は 500px です。

  • 400 / 500 = 0.8
  • 800 / 500 = 1.6
  • 1200 / 500 = 2.4

多くの場合、DPR 以上のもっとも近い比率の画像が表示されます。例えば、デバイスの DPR が 1 であれば、1.6 である photo-medium.jpg が選択される可能性が高く、DPR が 2 であれば、2.4 である photo-large.jpg が選択される可能性が高いです。

ここで注意する点としては、srcset 属性に指定した値はブラウザに情報を提供しているだけで、指示ではないということです。DPR やズームレベル、通信状況などがブラウザ側で考慮され、候補リストから最適な画像が選択されます。

さて、この sizes 属性ですが、なぜ本来 CSS が扱うべき、見た目に関わる情報を HTML に書かなければならないのでしょうか。

ブラウザは、HTML ドキュメントの解析を開始してからレンダリングが完了する前に画像を読み込み始めますが、その段階では、画像がどのようなレイアウトで配置され、どれぐらいのサイズで収まるかがわかりません。sizes 属性で先にレイアウト情報を伝えて、ブラウザに適切な画像を選択してもらう必要があるのです。

メディアクエリの指定

見出し「メディアクエリの指定」

sizes 属性には、以下のようにメディアクエリを使用して複数の条件を列挙できます。

メディアクエリを指定した sizes 属性の例
<div class="grid">
  <div class="column">
    <img
      src="photo-small.jpg"
      srcset="
        photo-small.jpg 400w,
        photo-medium.jpg 800w,
        photo-large.jpg 1200w
      "
      sizes="
        (min-width: 1200px) 33.33vw,
        (min-width: 768px) 50vw,
        100vw
      "
      alt="代替テキスト"
      width="400"
      height="400"
    >
  </div>
</div>

<style>
@media (min-width: 768px) {
  .grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1200px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
</style>

この sizes 属性の指定は以下を意味しています。

  1. ビューポート幅が 1200px 以上であれば、画像の幅を 33.33vw で表示する(3 カラム)
  2. ビューポート幅が 768px 以上であれば、画像の幅を 50vw で表示する(2 カラム)
  3. いずれも一致しなければ、画像の幅を 100vw で表示する(1 カラム)

上記の例では、レイアウト上の余白は考慮されていませんが、sizes 属性には calc() も使用できるので、余白分を差し引いたサイズ指定も可能です。

sizes 属性に calc() を使用した例
<div class="grid">
  <div class="column">
    <img
      src="photo-small.jpg"
      srcset="
        photo-small.jpg 400w,
        photo-medium.jpg 800w,
        photo-large.jpg 1200w
      "
      sizes="
        (min-width: 1200px) calc(33.33vw - 40px),
        (min-width: 768px) calc(50vw - 30px),
        calc(100vw - 40px)
      "
      alt="代替テキスト"
      width="400"
      height="400"
    >
  </div>
  <!-- ... -->
</div>

<style>
.grid {
  margin-inline: 20px;
}
@media (min-width: 768px) {
  .grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 20px;
  }
}
@media (min-width: 1200px) {
  .grid {
    margin-inline: 40px;
    grid-template-columns: repeat(3, 1fr);
  }
}
</style>

非常に複雑なコードになりました 😵‍💫

実装コストとのトレードオフ

見出し「実装コストとのトレードオフ」

ここまでで見てきたように、sizes 属性に指定するコードは複雑で理解しづらく、画像の選択は各ブラウザに委ねられるので、指定した値が正しいかどうかの検証が難しいのも事実です。

また、レイアウトが変更になると、場合によっては sizes 属性の値の変更や画像の書き出しが必要になり、メンテナンスコストも高くなります。

これらの点を踏まえ、ビューポートベースのレスポンシブイメージの実装については、パフォーマンス最適化と実装コストの、どちらに重点を置くかによって判断したほうがよいでしょう。

将来的には、loading="lazy" で画像の遅延読み込みが指定されている場合には、sizes="auto" を指定することで、ブラウザ側で自動的にレイアウト幅を計算してくれるように計画されています。

<img
  src="photo-small.jpg"
  srcset="
    photo-small.jpg 400w,
    photo-medium.jpg 800w,
    photo-large.jpg 1200w
  "
  sizes="auto"
  alt="代替テキスト"
  width="400"
  height="400"
>

すでに仕様には盛り込まれており1、Google Chrome では積極的に開発が進んでいますが2、安心して使えるようになるまではもう少し時間を要するでしょう。

なお、sizes="auto" が使用できるのは、あくまでも loading="lazy" が指定された画像のみなので、LCP に相当するヒーローイメージ(メインビジュアル)などには使えません。

<img> 要素の sizes 属性ではメディアクエリを指定できますが、コンテナクエリには対応していません。後述するアートディレクションで触れる <source> 要素でも同様です。

もし、実現できたとしても、基準となるコンテナ要素(親要素)とコンテナクエリを指定している子要素の両方に sizes 属性を指定する必要があり、非常に複雑でメンテナンス性が悪くなります。

この問題は、画像の読み込みをできるだけ早く開始するか、適切なサイズの画像のダウンロードを優先するかという点で競合するため、簡単に答えは出ないようです3

なお、sizes="auto" が安心して使えるようになれば解消されますが、依然としてファーストビュー(Above the fold)に配置される画像には使用できません。

代替案としては、<img> 要素ではなく CSS の background-image を使用すれば、コンテナクエリに対応できますが、<img> 要素としての役割を失うため4、装飾的な画像に限定されます。加えて、ブラウザ側に画像の選択を任せる srcset 属性とは、意味合いが異なる点にも注意する必要があります。

DPR ベースでは、srcset 属性を使用して、候補となる画像を指定します。

DPR ベースの図。`srcset` 属性にサイズ違いの画像のリストを指定することで、デバイスのピクセル比に応じて画像が選択される

前述のビューポートベースと比較するとシンプルですが、画像を出し分ける条件がデバイスのピクセル比(DPR)のみのため、さまざまなサイズに対応することはできません。

DPR ベースのレスポンシブイメージの例
<img
  src="[email protected]"
  srcset="[email protected] 2x"
  alt="代替テキスト"
  width="400"
  height="400"
>

ビューポートベース同様に、srcset 属性で候補となる画像ファイルのパスをカンマ区切りで列挙します。ディスクリプタ x は、デバイスピクセル比(DPR)を表します。

x ディスクリプタと、w ディスクリプタを混在させることはできません。

繰り返しになりますが、srcset 属性に指定した値はブラウザへのヒントに過ぎないので、2x を指定したからといって、必ずしも DPR が 2 のディスプレイで表示されるとは限りません。

背景画像(background-image)

見出し「背景画像(background-image)」

CSS の背景画像(background-image)で同様のことを実現するには、image-set() 関数を使用します。

.bg {
  background-image: url('photo.jpg');
  background-image: image-set(
    url('[email protected]') 1x,
    url('[email protected]') 2x
  );
}

なお、最初の background-image: url() はフォールバック用の指定です。

3. アートディレクション

見出し「3. アートディレクション」

レスポンシブイメージにおけるアートディレクションとは、<picture> 要素を使用して、メディアクエリに応じて画像を切り替える手法です。

アートディレクションの図。`<picture>` 要素を使用することで、メディアクエリに応じて画像を切り替えることができる

例えば、ヒーローイメージ(メインビジュアル)の画角やアスペクト比が、モバイル向けとデスクトップ向けで異なる場合、以下のように <source> 要素と <img> 要素を <picture> 要素で囲うことで、ブレイクポイントを境界に画像を出し分けることができます。

注意点として、指定できる alt 属性は 1 つなので、内容の異なる画像を指定すべきではありません。

<picture> 要素で画像を切り替える例
<picture>
  <source
    media="(min-width: 768px)"
    srcset="photo-desktop.jpg"
    width="1200"
    height="600"
  >
  <img
    src="photo-mobile.jpg"
    alt="代替テキスト"
    width="800"
    height="800"
  >
</picture>

このとき、<source> 要素内の media 属性が参照され、条件に一致する場合には srceset の属性の画像が表示され、一致しない場合には <img> 要素に指定した src 属性の画像が表示されます。

また、<source> 要素にも width 属性と height 属性を指定することができるので、画像のアスペクト比が異なる場合には、レイアウトシフトを防止するために指定します。

なお、前述した「1. ビューポートベース」や「2. DPR ベース」と大きく異なる点は、<source> 要素は指示的であるという点です。

前者の場合は、あくまで候補となる画像のリストを伝えるだけなので、画像選択の決定権はブラウザ側にあります。対して <source> 要素では、ブラウザは指定された内容に従うので、決定権は実装者にあります。

これは、img 要素で srcset 属性や sizes 属性を使用する場合とは異なります。その場合、ブラウザに候補が表示されます。source 要素は、候補というよりコマンドのようなものです。

The picture element | web.dev

<source> 要素にはメディアクエリが使用できるので、prefers-color-scheme を使用することで、ライトモードとダークモードで画像を出し分けることが可能です。

<picture> 要素でダークモードに対応する例
<picture>
  <source
    media="(prefers-color-scheme: dark)"
    srcset="photo-dark.jpg"
  >
  <source
    media="(prefers-color-scheme: light)"
    srcset="photo-light.jpg"
  >
  <img
    src="photo-light.jpg"
    alt="代替テキスト"
    width="400"
    height="400"
  >
</picture>

同様に、prefers-reduced-motion を使用することで、アニメーションを好まないユーザ向けに静止画を表示することができます。

<picture> 要素でアニメーションに対応する例
<picture>
  <source
    media="(prefers-reduced-motion: reduce)"
    srcset="photo-static.jpg"
  >
  <img
    src="photo-animation.gif"
    alt="代替テキスト"
    width="400"
    height="400"
  >
</picture>

ただ、情報を伝えるためにアニメーションが必須で、静止画では表現できない場合には、何かしらの代替コンテンツを用意する必要があります。

4. 画像フォーマットベース

見出し「4. 画像フォーマットベース」

前述のアートディレクションでは、<source> 属性にメデイアクエリを指定しましたが、type 属性を指定して、複数のフォーマットの画像を指定できます。

<picture> 要素で複数のフォーマットの画像を指定する例
<picture>
  <source type="image/avif" srcset="photo.avif">
  <source type="image/webp" srcset="photo.webp">
  <img src="photo.jpg" alt="代替テキスト" width="800" height="600">
</picture>

この例では、<source> 要素に type 属性を指定して、この条件に一致する画像を表示しています。指定した順番に、まず AVIF 形式に対応しているか、一致しなければ WebP 形式に対応しているかが評価されます。

そして、いずれの条件にも一致しない場合には、<img> 要素に指定した JPEG 形式の画像が表示されます。

背景画像(background-image)

見出し「背景画像(background-image)」

DPR ベースと同じように、CSS の背景画像(background-image)で同様のことを実現するには、image-set() 関数を使用します。

.bg {
  background-image: url('photo.jpg');
  background-image: image-set(
    url('photo.avif') type('image/avif'),
    url('photo.webp') type('image/webp')
  );
}

この記事では、レスポンシブイメージの 4 つのパターンを説明しました。

特に srcset 属性と sizes 属性を組み合わせたレスポンシブイメージはわかりづらく、画像を生成するコストまで考えると、採用できるプロジェクトは限定されるかもしれません。

将来的には sizes="auto" が使えるようになれば負担は軽減されますが、コンテナクエリを使用した場合にはどうするのかといった別の問題も生まれています。

このように、導入にあたって障壁の多いレスポンシブイメージですが、ウェブパフォーマンスにおいて効果的なメソッドの 1 つであることは間違いないでしょう。

ウェブサイト全体におけるモバイルのトラフィックの比率が、引き続き増加傾向にあることや5、ウェブサステナビリティへの対応が強く求められる時代において、パフォーマンス最適化は無視できない課題です。

まずは「必要以上に画像を使わない」「SVG 形式を積極的に使用する」といった戦略からスタートし、「LCP に影響しやすい要素から優先的にレスポンシブイメージを実装する」といったように、できるところから取り入れていくのがよいかもしれません。

本記事の作成にあたり、以下のウェブページを参考にしました。

脚注

  1. 4.8.4.2.2 Sizes attributes | HTML Standard

  2. Auto Sizes for Lazy Loaded Images with Srcset | Chrome Platform Status

  3. [css-conditional] [css-contain] srcset and sizes interaction with container queries | Issue #5889 | w3c/csswg-drafts

  4. role="img"aria-label 属性を使えば、画像として認識させることはできますが、ハック色が強すぎるため避けた方がよいでしょう。

  5. Traffic from mobile versus desktop | The 2022 Web Almanac