結論からいうと、以下のように違います。
srcset
属性・・・”異なるサイズの同じ画像”を表示したい場合に使うpicture
タグ・・・”異なる画像”を表示したい場合に使う
図解すると以下のような感じです🤗
srcset属性
例えば、スマホ端末に対して1920×1080の画像を表示するのはムダです。
もっと小さいサイズでいいですからね。
逆にデスクトップPCだと画面が大きいので、1920×1080の画像を表示しても良いですよね。
このように
- デスクトップPC
→1024×682の画像を表示する
- ノートパソコンPC
→640×426の画像を表示する
- スマホ
→400×266の画像を表示する
という風に、大きさの異なる画像を出し分けたいときに使うのがsrcset
属性です。
srcset属性のデモページ(ソース)
※後述していますが、ChromeやSafariの場合はスーパーリロードしないと画像が切り替わりません
例えば、以下のように書くと
<img srcset="small.png 400w, medium.png 640w, large.png 1024w" src="large.png" />
imgタグは、ブラウザに対して以下のように提案します。(後ろについているw
については後述します)
やぁよく来たね。
さっそくだけど君は以下の3つのサイズから選ぶことができるよ。
- small.pngの横幅:400px
- medium.pngの横幅:640px
- large.png:1024px
君の環境に合う画像だけを持っていくといいよ。
もし、srcset属性を理解できない場合はsrc属性を使ってね。(IE向け)
この場合、ブラウザは
- ブラウザの横幅が400px以下なら400pxに足りる最小の画像を探す
→small.png
を選ぶ
- ブラウザの横幅が640px以下なら640pxに足りる最小の画像を探す
→medium.png
を選ぶ
- ブラウザの横幅が1024px以下なら1024pxに足りる最小の画像を探す
→large.png
を選ぶ
- ブラウザの横幅が1024px以上ならsrcsetに記述されている最大の画像を探す
→large.png
を選ぶ
という動きをします。
しかしながら、常にブラウザの横幅いっぱいいっぱい(=100%)の画像がほしいとは限りません。
そういうときに使えるのがsizes
属性です。
例えば、以下のように指定すると
<img srcset="small.jpg 400w, medium.jpg 640w, large.jpg 1024w" sizes="(max-width: 700px) 400px, (max-width: 900px) 640px, (max-width: 1300px) 1024px, 100vw" 100vw">
- ブラウザの横幅が700px以下なら
→imgタグは「この画像は400pxで表示すべきやと思うやで」とブラウザに伝える
→結果的にブラウザは「ほなsmall.png
を使ったらええな。あと横幅は400pxで表示したらええな」と判断する
- ブラウザの横幅が900px以下なら
→imgタグは「この画像は640pxで表示すべきやと思うやで」とブラウザに伝える
→結果的にブラウザは「ほなmedium.png
を使ったらええな。あと横幅は640pxで表示したらええな」と判断する
- ブラウザの横幅が1300px以下なら
→imgタグは「この画像は1024pxで表示すべきやと思うやで」とブラウザに伝える
→結果的にブラウザは「ほなlarge.png
を使ったらええな。あと横幅は1024pxで表示したらええな」と判断する
- どれにも該当しないなら
→imgタグは「この画像は100vwで表示すべきやと思うやで」とブラウザに伝える
→結果的にブラウザは「ほなlarge.png
を使ったらええな。あと横幅は100vwで表示したらええな」と判断する
のような動きになります。
ただし、ここで注意しなければいけないのは、ブラウザによって以下のように挙動が異なることです。
- Chrome : 大きなサイズの画像ファイルをキャッシュした場合、画面幅を狭めても小さい画像は読み込まれない
- Firefox : 画面幅を変える度に、画面幅に適したサイズの画像を読み込む
- Safari : 最初に開いた画面幅に応じた画像ファイルがキャッシュされ、画面幅を変えても画像は再読み込みされない
これはつまりですね。
例えばChromeの場合、ブラウザの横幅が1920px以上の状態でアクセスすると「large.png
を取得したろ!」と判断しますが
そのあとにブラウザのサイズを小さくして、横幅が200px以下になったとしてもChromeは「大は小を兼ねるって言うし、キャッシュしたlarge.png
を使いまわしたらええやろ!」と判断して、small.png
は取得してくれません😂
先ほどのデモページで試してみてください。(ただし、ページをスーパーリロードしないとキャッシュが破棄されないので注意です)
Safaiも違った動きをします。
例えばSafariブラウザの横幅が200px以下の状態でアクセスすると「small.png
を取得したろ!」と判断しますが
そのあとにブラウザのサイズを大きくして、横幅が1920px以上になったとしてもSafaiは「画質汚いけどキャッシュしたsmall.png
使っときゃええやろ!通信量を節約するほうが大事やで!」と判断して、large.png
を取得してくれません😂
また、将来的にブラウザに「ネットワーク使用量を極力抑えるモード」なるものが実装されたとして、そのモードをONにすると「srcset
属性で提示された中から一番小さいサイズを必ず選ぶ」という動きになったりするかもしれません。
例えば、imgタグが「この画像は1920pxで表示すべきやと思うやで」とブラウザに伝えたとしても、「お前のいうことなぞ聞かぬ!ワイは一番サイズの小さいヤツを取得するやで!」みたいなことになるかもしれません。
こういう特性があるため、srcset
属性だけでは
- 大きい画面のユーザーには、犬の画像を表示させたろ!
- 小さい画面のユーザーには、猫の画像を表示させたろ!
みたいな切り替えが”確実”に実装できるとは限りません。
これは「srcset
属性で出し分ける画像は、大きさは違えど画像の中身は同じ」を前提にしているからです😎
pictureタグ
さきほどのように、
- 大きい画面のユーザーには、犬の画像を表示させたろ!
- 小さい画面のユーザーには、猫の画像を表示させたろ!
を”確実”に実装するために使うのがpicture
タグです。
以下のようにpicture
タグとsource
タグを組み合わせて使用します。
<picture> <source media="(max-width: 350px)" srcset="cat.png" /> <source media="(max-width: 700px)" srcset="dog.png" /> <img src="dog.png" /> </picture>
上のコードは
- ブラウザの横幅が350px以下なら
→「cat.png
を使え」とブラウザに強制する
- ブラウザの横幅が700px以下なら
→「dog.png
を使え」とブラウザに強制する
- どれにも該当しないなら
→「dog.png
を使え」とブラウザに強制する
のような意味になります。
pictureタグ と srcset属性
picture
タグとsrcset
属性を組み合わせて、以下のように書くこともできます。
<picture> <source media="(max-width: 350px)" srcset="cat-small.png 720w, cat-medium.png 1280w, cat-large.png 1920w"/> <source media="(max-width: 700px)" srcset="dog-small.png 720w, dog-medium.png 1280w, dog-large.png 1920w" /> <img src="dog.png" /> </picture>
上のコードは
- ブラウザの横幅が350px以下なら
→「以下の3つから好きなものを選べ」とブラウザに強制する
・cat-small.png
:横幅720px
・cat-medium.png
:横幅1280px
・cat-large.png
:横幅1920px
- ブラウザの横幅が700px以下なら
→「以下の3つから好きなものを選べ」とブラウザに強制する
・dog-small.png
:横幅720px
・dog-medium.png
:横幅1280px
・dog-large.png
:横幅1920px
- どれにも該当しないなら
→imgタグは「dog.png
を使え」とブラウザに強制する
のような意味になります。
また、source
タグは一番最初にマッチしたものが適用され、あとは無視されます。(最終的にどれにも該当しなかったら<img src="dog.png" />
が適用されます)
なので順番が重要です。
ちなみに、type
属性を使えば「webp非対応ならjpgを使う」という風にもできます。
<picture> <source type="image/webp" srcset="hoge.webp"> <img src="hoge.jpg"> </picture>
デバイスピクセル比の話
話は変わりますが
最近のスマホは、超高解像度になってきており、下手するとノートパソコンより解像度が高かったりします。
例えば、「Galaxy S21 Ultra 5G」というスマホだと、約7インチの小さい画面のくせに解像度が「3200 x 1440」もあったりして、「誰がこんなもん求めてんだ」と突っ込みたくなる解像度です😅
それはさておき、この「3200 x 1440」という解像度をそのまま使ってしまうと、ブラウザで表示される文字などが極小サイズになってしまって、とても使い物にならなくなってしまいます。
そこで「ブラウザでは2 x 2ピクセルを1ピクセルとして扱うようにすればいいのでは?」と誰かが考えました。
おそらく最初にこれを実現したのはAppleだと思うのですが、Appleではこれを「Retinaディスプレイ」などと呼んでいます。
(他のAndroidスマホでは「高精細ディスプレイ」などと呼ぶみたいです)
それはさておき、ここでいう「複数のピクセルを1ピクセルとして扱うようにすればいいのでは?」という考えで設定された
- 実際のデバイスのピクセル数(3200 x 1440)
→デバイスピクセルと呼ぶらしい
- ブラウザ上での疑似ピクセル数(1600 x 720)
→CSSピクセルと呼ぶらしい
との比率のことを「デバイスピクセル比」と呼びます。(この場合のデバイスピクセル比は「2」です)
つまり
CSSピクセル × デバイスピクセル比 = デバイスピクセル
という式になります。
例えば、iPhoneXは
- 解像度:
1125x2436
- デバイスピクセル比:
3.5
と設定されているので、ブラウザでは375x812
として認識されます。
では、ここで問題です。
iPhoneXで以下のコードを実行すると、どちらの画像が読み込まれるのでしょうか?
<img srcset="small.png 375w, medium.png 1125w" src="medium.png" />
ブラウザでは375x812
として認識されるんだから、small.png
の方が読み込まれるんじゃないの?
と思うかもしれませんが、実はmedium.png
の方が読み込まれます。
これは「普段はそのまま表示しちゃうと小さすぎて見にくいから2×2を1ピクセルとして表示してるけど、画像は1ピクセルを1ピクセルとして表示したほうが綺麗に見えるでしょ?」という理由からです。
つまり、画像を表示するときはCSSピクセルではなく、デバイスピクセルが使われます。(このへんがややこしい・・・)
w、x、h
さきほど以下のようなコードを書きましたが
<img srcset="small.png 400w, medium.png 640w, large.png 1024w" src="large.png" />
ここでいう
400w
640w
1024w
の後ろに付いているw
は「画像の幅」を表しています。
他にも
w
(画像の幅)h
(画像の高さ)x
(デバイスピクセル比)
も指定できます。
例えば、x
を使って以下のような書き方もできます。
<img srcset="small.png 1x, medium.png 2x, large.png 3x" src="medium.png" />
- デバイスピクセル比が1なら
→small.png
を選ぶ
- デバイスピクセル比が2なら
→medium.png
を選ぶ
- デバイスピクセル比が3なら
→large.png
を選ぶ
例えば、h
を使って以下のような書き方もできます。
<img srcset="small.png 400h, medium.png 720h, large.png 1080h" src="medium.png" />
- ブラウザの高さが400px以下なら400pxに足りる最小の画像を探す
→small.png
を選ぶ
- ブラウザの高さが720px以下なら720pxに足りる最小の画像を探す
→medium.png
を選ぶ
- ブラウザの高さが1080px以下以下なら1080pxに足りる最小の画像を探す
→large.png
を選ぶ
- ブラウザの横幅が1080px以上ならsrcsetに記述されている最大の画像を探す
→large.png
を選ぶ
最初に400w
という表記を見たときに「なんでpx
じゃないんだ?」と疑問に思ったかもしれませんが、pxだと「画像の高さ」を指定できないからだと思われます。(おそらく・・)
おわりに
この記事のような感じで自分は理解したのですが
もし間違っている箇所があれば、コメントが指摘していただけると大変うれしいです!🙇
おわり
コメント
コメント失礼します。srcset属性の説明に誤りがあるように思います。
「ブラウザの横幅が720px以上なら720px以上の画像がないか探す
→small.pngを選ぶ」
small.pngの幅は720pxとされているので、正しくはmedium.pngが選ばれるのではないでしょうか。
ブラウザの横幅が720px以下なら720pxに足りる最小の画像を探す
→small.pngを選ぶ
ブラウザの横幅が1280px以下なら1280pxに足りる最小の画像を探す
→medium.pngを選ぶ
ブラウザの横幅が1920px以下なら1920pxに足りる最小の画像を探す
→large.pngを選ぶ
ブラウザの横幅が1920px以上ならsrcsetに記述されている最大の画像を探す
→large.pngを選ぶ
が正しい挙動かと思います。
言われてみればそのとおりです!
ご指摘ありがとうございます!修正させていただきました!
あと、デモとして用意しているソースでは、画像サンプルが
・400
・640
・1024
となっているのに、記事中では
・720
・1280
・1920
となっていて、まぎらわしい気がしたので、それも前者に統一しました!
冒頭の結論からいうとの部分しか読んでませんが相当気になったのでコメントします
”異なる画像”を表示したい場合に使う というのは、少なくとも私の現場経験上は違います
理由は主に2つ。
一つ目、スマホとPCで外観の違う画像を使い分けるのはSEO上非常にまずいです。なぜならGoogleはスマホサイズの画面に対してインデックスしてくるからです。これは5年位前からそうなりました。昔はPCサイトも別途インデックスしていたのですが今は違います。なのでスマホとPCで同じ画像を極力使うのがユーザーエクスペリエンスに優しいサイトです。
二つ目、pictureタグは端末が対応しているファイルの種類の壁に対応するのに使います。
代表的なのはmacのsafariでwebpが表示されないというものです。これに対処するために、pictureでデフォルトをjpg等にして、srcsetでwebpを追加していきます。webpに対応するブラウザではwebpがよみこまれます。
天下のMDNさまでもpicture要素を使って2種類の画像を切り替えてる訳なんですけど…
https://developer.mozilla.org/ja/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
ろくに読まずに適当なコメントしてるし、失礼な人もいるもんですね。
どこよりも詳しい解説で助かりました!
自分の場合は同じ画像のサイズ違いを切り替えたかったのですが、srcsetだとやっぱりブラウザごとに挙動が結構変わるんですね…。
pictureとどう違うのかけっこう悩んでたのですが、おかげさまですっきりと違いを認識して適切に使い分けることができました。
ありがとうございました!
誤字報告なのですが、
・2個目のコードサンプル中で「100vw”」が重複しています
・プロフィールのリンク「penpen」がリンク切れしてます笑
以下、どうでもいいおまけ情報です。
~~~~
自分の場合は
「大きい画像を縮小表示したサムネイル画像について、普段は等倍表示する」
「Retinaディスプレイでは devicePixelRatio に合わせた倍率の等倍画像にする」
「ページ拡大した際にはその分だけ解像度を高くした画像にする」
というのを実現したくてあれこれ苦戦してました。
srcsetの横幅指定”123w”の代わりにdevicePixelRatio指定 img srcset=”**.jpg 2x” が可能なのですが、
・Firefoxだとズーム等で devicePixelRatio が変化してもちゃんと画像が切り替わる
・Chromiumだとズームで全く画像が切り替わらない(最初からズーム済なら反映される)
・iPhoneのSafariだとそもそもズームでdevicePixelRatioが変化しない
と、見事にブラウザによって意図しない挙動になる感じでした。
どうせ似たような処理なんですし、pictureとsource media=”**”で対応することにしました。
pictureの場合はChromiumでもちゃんとズームで画像が切り替わってくれます。(iPhoneはムリ)
印刷用の画像も設定できるし一石二鳥!
コメントありがとうございます!そう言って頂けると書いた甲斐があります🙏
誤字報告もありがとうございます!(修正しました)