pictureタグで画像を画面サイズごとに切り替えるレスポンシブ表現【デモ付き】


picture は、画面幅や対応フォーマットに応じて表示する画像ファイルそのものを差し替えるHTML要素です。同じ絵柄のサイズ違いを選ばせたいだけなら picture は不要で、imgsrcset を使います。picture の出番は「絵柄そのものを変えたいとき」と「対応していないブラウザ向けに別形式を用意したいとき」の2つです。

この記事では picture の基本形から、source の書き方で必ず守るべきルール、widthloading をどの要素に書くかまでを、実際のコードで解説します。最後に「本当に切り替わっているか」をブラウザのコンソールで1行判定する手順を置いたので、書いたらそのまま合否を確認できます。


pictureタグとは何か

picture は、0個以上の source 要素と1つの img 要素を含み、画面や端末の条件に応じた画像を提供する要素です。ブラウザは source を上から順に評価し、条件に合う最初の1つを採用します。合うものが1つも無ければ imgsrc が使われます(MDN: picture要素)。

ここで押さえておきたいのは、picture が切り替えているのはCSSの見た目ではなくファイルそのものだという点です。CSSの background-image でも似たことはできますが、その場合は alt を持てず、画像がコンテンツではなく装飾として扱われます。

srcset は解像度切り替え、picture はアートディレクション

この2つを混同すると、書かなくてよい picture を書くことになります。判断は「絵柄が変わるかどうか」の一点です。

やりたいこと使う要素誰が選ぶか
同じ絵柄のまま、画面や端末に合ったサイズのファイルを落としたいimgsrcsetsizesブラウザに任せる
高DPI(Retina)ディスプレイ向けに高解像度版を出したいimgsrcset2x などの記述子)ブラウザに任せる
画面幅によって絵柄そのもの(トリミング・構図)を変えたいpicturesourcemedia作り手が指示する
対応していないブラウザ向けに別の画像形式を用意したいpicturesourcetype作り手が指示する

2行目は特に間違えやすいところです。高解像度ディスプレイ向けの出し分けは picture の仕事ではありません。MDNは「DPIの高い(高解像度の)ディスプレイのために高解像度版の画像を提供する場合は、代わりに srcset 属性を img に使用してください」と明記しています。imgsrcset に任せておけば、ブラウザはデータ節約モードで低解像度版を選ぶこともでき、media 条件を自分で書く必要もなくなるためです。レスポンシブ全体の中での位置づけはレスポンシブデザイン入門で整理しています。

pictureの正しい用途は3つ

MDNが挙げている picture の用途は次の3つです。

  1. アートディレクションmedia の条件に合わせて画像を切り抜いたり変更したりする。狭い画面では要素の少ない簡潔な絵柄に差し替える、など
  2. 代替画像形式の提供 — WebPやAVIFに対応していないブラウザへ、JPEG/PNGを渡す
  3. 通信帯域の節約 — 見る人の画面に最も適合する画像を読み込ませ、ページの読み込みを速くする

3つ目は、狭い画面に大きなファイルを落とさないという意味です。効果を出すには出し分ける先のファイルを実際に軽くしておく必要があり、同じ原寸の画像をファイル名だけ変えて並べても速度は1バイトも改善しません。画像そのものの軽量化は画像圧縮ツール画像圧縮ツールの比較で扱っています。


基本の書き方と、外せない4つのルール

picture は書き方の自由度が低く、外すと黙って動かなくなる箇所がはっきりしています。まず基本形を示し、そのあとで守るべき4点を1つずつ潰します。

基本形

PC・タブレット・スマホの3段で絵柄を差し替える最小構成です。

<picture>
  <source media="(min-width: 1024px)" srcset="hero-wide.jpg">
  <source media="(min-width: 768px)" srcset="hero-mid.jpg">
  <img src="hero-square.jpg" width="800" height="800"
       decoding="async"
       alt="製品を手に取る利用者">
</picture>

1024px以上なら hero-wide.jpg、768px以上1024px未満なら hero-mid.jpg、それ未満なら imghero-square.jpg が表示されます。source は終了タグを持たない空要素なので、</source> は書きません。

ルール1: sourceにはsrcsetが必須で、srcは書けない

初学者が最も多く踏むのがこれです。videoaudio の中の sourcesrc で書くため、同じ感覚で picture の中に src を書いてしまいます。しかしMDNの source 要素の仕様上、src は「親が picture の場合は許可されない」、srcset は「親が picture の場合は必須」と定められています(MDN: source要素)。

<!-- NG: picture の中の source に src は書けない。この source は無視される -->
<source media="(min-width: 768px)" src="hero-wide.jpg">

<!-- OK: picture の中では srcset が必須 -->
<source media="(min-width: 768px)" srcset="hero-wide.jpg">

厄介なのは、これを間違えてもエラーが出ないことです。その source が黙って無視され、常に img の画像が表示され続けます。「なぜか切り替わらない」ときは真っ先にここを見てください。

ルール2: 記述順は「広い条件から」— 一般論ではなく必須

ブラウザは source を上から順に評価し、条件が false ならその要素をスキップして次を評価します。そして最初に一致したものをそのまま採用し、以降は見ません。つまり min-width で条件を書く場合、広い条件を先に書くのは「そうするのが一般的」なのではなく、そうしないと成立しません。

<!-- NG: 768px の条件が先にあるため、1200px でも hero-mid.jpg が採用される。
        hero-wide.jpg は永久に表示されない -->
<picture>
  <source media="(min-width: 768px)" srcset="hero-mid.jpg">
  <source media="(min-width: 1024px)" srcset="hero-wide.jpg">
  <img src="hero-square.jpg" width="800" height="800" alt="製品を手に取る利用者">
</picture>

このNG例を実際にブラウザで開いて幅1200pxで確認したところ、採用されたファイルは hero-mid.jpg のままでした。見た目には画像が出ているので、指摘されるまで気づけません。逆に max-width で書くなら、狭い条件から先に並べます。

ルール3: imgは代替であると同時に、描画される本体

img を「条件に合わなかったときの保険」とだけ理解していると、次のルール4でつまずきます。MDNは img2つの役割を担うと説明しています。

  1. 画像の寸法やその他の属性と、表示方法を記述する
  2. source 要素で利用可能な画像を提供できなかった場合の代替策を提供する

重要なのは1つ目です。source が当たった場合も、画面に描画されている要素は img のままで、ファイルの中身だけが差し替わります。source は「どのファイルを使うか」を決めるだけで、それ自体が表示される要素ではありません。だから寸法・代替テキスト・読み込み方といった属性は、すべて img 側が担います。

ルール4: alt・width・height・loading・decoding はimgに書く

ルール3の帰結として、属性の置き場所が決まります。altimg に1つだけ書きます。sourcealt は書けないので、絵柄が変わっても代替テキストは共通です。どの絵柄でも成り立つ説明文にしておきます。

属性書く場所役割
altimg のみ(1つだけ)アクセシビリティと画像検索に必要な代替テキスト
width / heightimgsource にも指定可)読み込み前に縦横比を確保し、レイアウトのずれ(CLS)を防ぐ
loadingimg のみlazy で画面外の画像の読み込みを遅らせる
decodingimg のみasync でデコード待ちによる描画の詰まりを避ける
media / typesource のみどのファイルを採用するかの条件

widthheight は必ず書いてください。MDNは「heightwidth を含めると、画像が読み込まれる前にブラウザーが縦横比を計算でき、表示に必要な領域を確保できるため、レイアウトのずれが軽減または防止される」と説明しています(MDN: img要素)。このずれはCore Web VitalsのCLSとして計測される項目で、詳しくはCore Web Vitals改善ガイドにまとめています。

アートディレクションでは絵柄ごとに縦横比が変わるため、imgwidth / height だけでは差し替え後の比率とずれることがあります。source にも widthheight を書けるので、比率が変わる場合はそちらにも指定しておくと確保される領域が正確になります。なお object-fitobject-position で位置や収まりを調整する場合も、指定先は picture ではなく子の img です。

loading="lazy" の扱いには注意が必要です。ファーストビューのヒーロー画像に付けると読み込みが後回しになり、かえって表示が遅くなります。lazy を付けるのは画面外にある画像だけにしてください。ファーストビューの設計はファーストビューの作り方、表示速度全般はページ表示速度の改善ガイドで扱っています。


WebPやAVIFをtypeで出し分ける

もう1つの正規用途が、画像形式の出し分けです。sourcetype でMIMEタイプを書くと、ブラウザはサーバーに問い合わせる前にその形式を表示できるかを判定し、対応していなければサーバーへの問い合わせを省略して次の source へ進みます。

<picture>
  <source type="image/avif" srcset="hero.avif">
  <source type="image/webp" srcset="hero.webp">
  <img src="hero.jpg" width="1600" height="900"
       decoding="async"
       alt="オフィスで打ち合わせをする2人">
</picture>

新しい形式ほど上に書きます。AVIFに対応していればAVIF、対応していなければWebP、どちらも駄目なら img のJPEGが使われます。mediatype は同じ source に併記できるので、「広い画面ではワイド版のWebP」といった組み合わせも書けます。

この形の注意点は、imgsrc に書いた値が実際に表示されるとは限らないことです。上のコードをChromeで開いて確かめたところ、src 属性は hero.jpg のままなのに、実際に読み込まれたファイルは hero.avif でした(AVIFに対応していないブラウザなら hero.webp が使われます)。HTMLを目で読んでも「いま何が出ているか」は分かりません。次のセクションの確認手順が要る理由がここにあります。


意図どおり切り替わったかを確かめる

picture は「書けば動いているように見えてしまう」要素です。見た目が変わらないだけで、実際には1枚目の source がずっと当たり続けている、ということが起こります。書いたあとは必ず、ブラウザが最終的にどのファイルを選んだかを目で確認してください。

コンソールで1行判定する

DevToolsを開き(Windowsは F12、Macは Command + Option + I)、Consoleで次の1行を実行します。そのあとウィンドウ幅を境界値の前後(例: 767px と 768px)に変えて、そのつど再実行してください。

document.querySelector('picture img').currentSrc

currentSrc は、ブラウザが実際に選択して読み込んだ画像のURLを返すプロパティです(MDN: HTMLImageElement.currentSrc)。src はHTMLに書いた文字列がそのまま返るだけなので、検証には使えません。DevToolsの基本操作はChrome DevToolsでの検証手順にまとめています。

合否ライン

観測できる事実判定疑うところ
境界の前後で currentSrc のファイル名が変わる合格
幅を変えても currentSrc が最初の1枚から変わらない不合格source の記述順。min-width を使うなら広い条件から先に書く
currentSrc が常に imgsrc と同じ不合格sourcesrc を書いていないか。picture の中では srcset が必須
DevToolsのNetworkに、表示していないはずの画像も並ぶ要確認一度読み込んだファイルは幅を戻しても再取得されない。判定はリロード後の1回目で行う

ハマりどころ

幅を変えただけでは、すでに読み込み済みのファイルは読み込み直されません。「切り替わらない」と感じたときは、幅を変えてからリロードして1回目の状態で確認してください。

これは特にNetworkパネルを見ているときに効いてきます。実際に幅を1200pxから500pxへ、さらに1200pxへ戻して計測したところ、戻したときの画像リクエストは0件でした。currentSrc の値自体は正しく hero-wide.jpg に戻っているのに、Networkには新しい行が1つも増えません。すでに取得済みのファイルが再利用されるためです。Networkの行数で判断せず、合否は currentSrc の値で取ってください。


デモで切り替わりを体験する

以下のデモでは、ボックスの横幅によって画像が自動で切り替わる様子を確認できます。リサイズ可能なボックス内で画面サイズを仮想的に再現しているため、ブラウザ全体を伸縮させなくても、記事内だけで切り替え挙動を確かめられます。

なお、このデモは「切り替わること」の確認用であり、ファイルサイズの最適化例ではありません。使っている3枚はいずれも原寸のまま並べたサンプル画像で、狭い画面用のほうが広い画面用より重い組み合わせも含まれています。実務では、出し分ける先のファイルをそれぞれの表示サイズに合わせて書き出し、圧縮までかけてから使ってください。

差分チェックツールで効率UPお手本コードと自分のコードを比較して、違いを一目で確認できます。練習前にブックマークしておくと便利です。
Diff Checkerを開く →

See the Pen picture-tag by masakazuimai (@masakazuimai) on CodePen.

デモで挙動をつかんだら、自分のページに書いたうえで前セクションの currentSrc で合否を取るところまでやってください。デモ上で切り替わって見えることと、自分のコードが正しいことは別の話です。CSSまわりの実装パターンはCSSテクニック集にまとめています。


よくある質問(FAQ)

Q. pictureタグとsrcsetの違いは?

srcsetは同じ画像の異なるサイズを提供し、ブラウザが最適なものを選択します。pictureタグはアートディレクション(異なる構図の画像の出し分け)に使います。モバイルでは横長→正方形に切り替えるような場合はpictureタグが適切です。

Q. sourceにsrcを書いても動きますか?

動きません。MDNのsource要素の仕様では、srcは親がpictureの場合は許可されず、srcsetが必須と定められています。srcを書いたsourceは黙って無視され、常にimg側の画像が表示され続けます。エラーは出ないため、切り替わらないときはまずここを確認してください。

Q. altやwidthはpictureとimgのどちらに書きますか?

すべてimg側に書きます。altはimgに1つだけ書き、sourceには書けません。width・height・loading・decodingもimgに書きます。sourceが当たった場合も描画されている要素はimgのままで、ファイルの中身だけが差し替わるためです。なおwidthとheightはsourceにも指定でき、絵柄ごとに縦横比が変わる場合は併記すると領域確保が正確になります。

Q. 切り替わらないときはどこを見ればいいですか?

Consoleで document.querySelector(‘picture img’).currentSrc を実行し、幅を変えながら値が変わるかを見ます。値が最初の1枚から動かない場合はsourceの記述順(min-widthなら広い条件を先に書く)、値が常にimgのsrcと同じ場合はsourceにsrcを書いていないかを疑います。Networkパネルは一度取得したファイルを再取得しないため、判定にはcurrentSrcを使ってください。

Q. 高解像度ディスプレイ向けの画像もpictureで出し分けますか?

いいえ、その用途はimgのsrcsetが担当します。MDNは、高DPIディスプレイ向けに高解像度版を提供する場合は代わりにsrcset属性をimgに使うよう明記しています。srcsetに任せればブラウザがデータ節約モードで低解像度版を選べるうえ、media条件を自分で書く必要もなくなります。pictureを使うのは絵柄そのものを変える場合と、形式を出し分ける場合です。

Q. WebP画像への対応にpictureタグは必要ですか?

古いブラウザへのフォールバックが必要な場合はpictureタグでWebPとJPEG/PNGを出し分けます。ただし主要ブラウザはすでにWebPに対応しているため、imgタグのsrc属性にWebPを直接指定する方法でも実用上は問題ありません。対応状況はMDNのブラウザー互換性表で確認できます。