レスポンシブ画像の実装ガイド - srcset, sizes, picture 要素の完全解説
レスポンシブ画像が必要な理由
現代の Web では、同じページが 320px 幅のスマートフォンから 3,840 px 幅の 4K モニターまで、多様なデバイスで閲覧されます。すべてのデバイスに同じ画像を配信すると、モバイルユーザーには不必要に大きなファイルをダウンロードさせ、高解像度ディスプレイのユーザーにはぼやけた画像を表示することになります。どちらのケースもユーザー体験を損ない、ビジネス指標に悪影響を与えます。
具体的な数値で考えてみましょう。幅 1,920 px の画像は約 300KB ですが、モバイルで表示する際に必要なのは幅 640px (約 50KB) 程度です。年間 100 万 PV のサイトでモバイルユーザーが 60% だとすると、レスポンシブ画像を実装するだけで月間約 150GB のデータ転送量を削減できます。これは CDN コストの削減だけでなく、ユーザーの通信量節約にも直結します。
Google の Core Web Vitals においても、画像の最適化は LCP (Largest Contentful Paint) スコアに大きく影響します。ファーストビューに表示される画像が適切なサイズで配信されていないと、LCP が悪化し検索順位に影響する可能性があります。レスポンシブ画像は単なる最適化テクニックではなく、現代の Web 開発における必須要件です。
srcset 属性による解像度の切り替え
srcset 属性は、ブラウザに複数の画像候補を提示し、デバイスの条件に応じて最適なものを選択させる仕組みです。記述方法には「幅記述子 (w)」と「ピクセル密度記述子 (x)」の 2 種類があります。
幅記述子を使用する場合、各画像の実際のピクセル幅を指定します:
<img srcset="photo-640w.jpg 640w, photo-1024w.jpg 1024w, photo-1920w.jpg 1920w" sizes="(max-width: 768px) 100vw, 50vw" src="photo-1024w.jpg" alt="風景写真">
ピクセル密度記述子は、Retina ディスプレイ対応に特化した記法です:
<img srcset="logo.png 1x, logo@2x.png 2x, logo@3x.png 3x" src="logo.png" alt="ロゴ">
幅記述子と sizes 属性の組み合わせが推奨される理由は、ブラウザがビューポート幅とデバイスピクセル比の両方を考慮して最適な画像を選択できるためです。例えば、375px 幅の iPhone (2x) で sizes="100vw" の画像を表示する場合、ブラウザは 750w 以上の画像を選択します。これにより、1 つの srcset 宣言で解像度とビューポートサイズの両方に対応できます。
sizes 属性の正しい書き方
sizes 属性は、画像が各ブレークポイントでどのくらいの幅で表示されるかをブラウザに伝えます。ブラウザはこの情報と srcset の候補リストを組み合わせて、ダウンロードすべき画像を決定します。重要なのは、sizes はレイアウトが確定する前 (CSS の読み込み前) にブラウザが参照するため、CSS のブレークポイントと一致させる必要があるという点です。
sizes の構文はメディア条件とスロット幅のペアで構成されます:
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
この例では、ビューポート幅 600px 以下で画像幅 100vw、601px〜1,200 px で 50vw、それ以上で 33vw と宣言しています。ブラウザは最初にマッチした条件のスロット幅を使用し、デバイスピクセル比を掛けて必要な画像幅を算出します。
よくある間違いとして、sizes に固定ピクセル値を使用するケースがあります。sizes="300px" と書くと、すべてのビューポートで 300px 幅として計算されるため、レスポンシブの意味がなくなります。代わりに calc() を活用して sizes="(max-width: 768px) calc(100vw - 32px), calc(50vw - 48px)" のようにパディングを考慮した正確な値を指定すると、ブラウザの画像選択精度が向上します。
picture 要素によるアートディレクション
<picture> 要素は、srcset だけでは実現できない「アートディレクション」を可能にします。アートディレクションとは、画面サイズに応じて画像の構図やクロップを変更することです。例えば、デスクトップでは横長のパノラマ写真を表示し、モバイルでは被写体を中心にクロップした正方形の画像を表示するといった制御が可能です。
<picture> 要素の基本構造:
<picture><source media="(min-width: 1024px)" srcset="hero-wide.jpg"><source media="(min-width: 640px)" srcset="hero-medium.jpg"><img src="hero-mobile.jpg" alt="ヒーロー画像"></picture>
<source> 要素の media 属性はメディアクエリを受け取り、条件に一致する最初の <source> が使用されます。srcset と異なり、<picture> ではブラウザに選択の余地がなく、条件に一致した <source> が強制的に使用されます。これがアートディレクションに適している理由です。
さらに、<picture> は画像フォーマットの出し分けにも使用できます。WebP や AVIF に対応したブラウザには次世代フォーマットを、非対応ブラウザには JPEG を配信する構成が一般的です。type 属性で MIME タイプを指定することで、ブラウザは対応するフォーマットを自動的に選択します。
実装のベストプラクティスとパフォーマンス最適化
レスポンシブ画像を実装する際の具体的なベストプラクティスを紹介します。まず、画像のブレークポイント (生成するサイズのバリエーション) は 3〜5 段階が適切です。あまりに多くのバリエーションを用意すると CDN のキャッシュ効率が低下し、少なすぎると最適化の効果が薄れます。
推奨するサイズバリエーション:
- 640w: モバイル (1x) 向け
- 960w: モバイル (2x)、タブレット (1x) 向け
- 1280w: タブレット (2x)、デスクトップ (1x) 向け
- 1920w: デスクトップ (2x) 向け
- 2560w: 4K ディスプレイ向け (必要に応じて)
LCP 対象の画像 (ファーストビューのヒーロー画像など) には fetchpriority="high" を付与し、ブラウザに優先的にダウンロードさせます。逆に、ファーストビュー外の画像には loading="lazy" を設定して遅延読み込みを有効にします。この 2 つの属性を適切に使い分けることで、初期表示速度を最大化できます。
width と height 属性を必ず指定し、ブラウザがレイアウトシフト (CLS) を防止できるようにします。アスペクト比が異なる画像を <picture> で出し分ける場合は、CSS の aspect-ratio プロパティと組み合わせて CLS を防ぎます。
ビルドパイプラインでの自動生成
レスポンシブ画像の複数サイズを手動で生成するのは非現実的です。ビルドパイプラインに画像処理を組み込み、自動的にリサイズ・フォーマット変換・最適化を行う仕組みを構築しましょう。
Node.js 環境では sharp ライブラリが最も高速で信頼性の高い選択肢です。以下のようなスクリプトで複数サイズを一括生成できます:
const sharp = require('sharp');const sizes = [640, 960, 1280, 1920];sizes.forEach(w => sharp('input.jpg').resize(w).jpeg({ quality: 82, progressive: true }).toFile(`output-${w}w.jpg`));
Next.js の next/image コンポーネントや Gatsby の gatsby-plugin-image は、ビルド時に自動的にレスポンシブ画像を生成し、適切な srcset と sizes を出力します。フレームワークの機能を活用することで、手動での HTML 記述を最小限に抑えられます。
CDN レベルでの画像変換サービス (Cloudflare Images、imgix、Cloudinary など) を利用する方法もあります。URL パラメータでサイズやフォーマットを指定するだけで、オリジン画像から動的にリサイズされた画像が配信されます。ビルド時の処理が不要になる反面、リクエストごとに変換コストが発生する (キャッシュミス時) ため、トラフィック量とコストのバランスを考慮する必要があります。