CSSのimage-set()で解像度に合う画像を出し分ける方法

​​CSS​Playwright,画像

CSS の背景画像を image-set() で書くと、画面の倍率(devicePixelRatio)に合わせて、ブラウザーが候補の中から1枚を選んで読み込みます。スマホや Retina ディスプレイのような高解像度の画面ではくっきりした2倍の画像を、通常の画面では軽い画像を出し分けられます。読み込まれるのは選ばれた1枚だけで、表示される大きさは、どちらが選ばれても同じです。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。Playwright で画面の倍率(deviceScaleFactor)を 1、1.5、2、3 に変えてページを開き、テスト用のサーバーにどの画像のリクエストが来たかを記録しました。倍率は Playwright のエミュレーションで変えたもので、実機の高解像度ディスプレイ、ページを開いたあとに倍率が変わる場合、Firefox と Safari の動きは試していません。対応バージョンは、互換性データ(@mdn/browser-compat-data 8.1.8)の値です。

1x と 2x の画像を並べて書く

用意するのは、同じ見た目で大きさが違う2枚の画像です。検証では、200×120 の cat-1x.png と、400×240 の cat-2x.png を作りました。どちらにも「1x 用」「2x 用」と書いてあり、どちらが表示されたかが見て分かるようにしています。

css
.hero {
  width: 200px;
  height: 120px;
  background-repeat: no-repeat;
  background-image: image-set(
    "cat-1x.png" 1x,
    "cat-2x.png" 2x
  );
}

画像の URL のあとに、その画像が何倍の画面向けかを 1x、2x のように書きます。URL は文字列のまま書いても、url("cat-1x.png") と書いても、同じように選ばれました。

画面の倍率を変えて開くと、サーバーに来たリクエストは次のとおりでした。

画面の倍率 読み込まれた画像
1 cat-1x.png だけ
1.5 cat-2x.png だけ
2 cat-2x.png だけ
3 cat-2x.png だけ

どの倍率でも、リクエストは1枚だけでした。使われない方の画像は読み込まれません。倍率 1.5 では、1x より細かい 2x が選ばれました。倍率 3 のように、用意した候補の中に画面の倍率に合うものがなければ、いちばん細かい 2x が使われました。

倍率2の画面で撮った2つの表示を縦に並べた画面。上は url("cat-1x.png") だけを指定したもので、「1x 用」の文字と斜めの線がぼやけている。下は image-set("cat-1x.png" 1x, "cat-2x.png" 2x) を指定したもので、「2x 用」の画像が選ばれ、文字と線がくっきりしている
倍率2の画面で、url() で1x用の画像だけを指定した場合(上)と、image-set() の場合(下)

倍率2の画面で、url() で1x用の画像だけを指定すると、200×120 の画像が引き伸ばされ、文字や線がぼやけました。image-set() では 2x 用の画像が選ばれ、同じ大きさの枠の中でもくっきり表示されています。

どの候補を選ぶかは、CSS Images の仕様ではブラウザーに任されています。仕様には、画面の解像度のほか、回線の速さなども判断の材料になりうると書かれています。上の表は、検証環境の Chromium 153 での結果です。

倍率の違う候補は、どの順番で書いてもよい

候補を "cat-2x.png" 2x, "cat-1x.png" 1x のように逆の順番で書いても、倍率1では cat-1x.png、倍率2では cat-2x.png が選ばれました。倍率の違う候補どうしなら、並べる順番は結果に影響しませんでした。

倍率の指定を省いた候補は、1x として扱われました。image-set("cat-1x.png", "cat-2x.png" 2x) を倍率1で開くと、cat-1x.png が読み込まれました。仕様でも、倍率を書かない候補は 1x として選ぶと定められています。

倍率は x のほかに、dppx や dpi の単位でも書けます。1dppx と 2dppx、96dpi と 192dpi で書いても、倍率2で cat-2x.png が選ばれました。getComputedStyle() で値を読むと、どの書き方でも image-set(url("cat-1x.png") 1dppx, url("cat-2x.png") 2dppx) になっていました。

2x の画像は半分の大きさで表示される

image-set() で 2x と書いた画像は、画像の実際のピクセル数の半分の大きさで表示されます。400×240 の cat-2x.png を、幅 500px・高さ 300px の要素の背景にして、background-size は指定せずに、画像が描かれた範囲を測りました。

指定 画像が描かれた範囲
image-set("cat-1x.png" 1x, "cat-2x.png" 2x) 200×120
url("cat-2x.png") 400×240
image-set("cat-2x.png" 1x) 400×240

同じ cat-2x.png でも、image-set() で 2x と書いたときだけ、1x 用の画像と同じ 200×120 で描かれました。倍率1で cat-1x.png が選ばれたときも、描かれた範囲は 200×120 でした。仕様では、候補に書いた倍率が、その画像の本来の解像度として扱われると定められています。そのため、倍率ごとに background-size を書き分けなくても、どちらが選ばれても同じ大きさになります。

MDN の <image> のページにあるとおり、image-set() は CSS で画像を指定する値(<image>)の1つです。background-image のほかに、content プロパティでも使えました。要素に content: image-set("cat-1x.png" 1x, "cat-2x.png" 2x) を指定して倍率2で開くと、cat-2x.png が読み込まれ、要素の大きさは 200×120 になりました。content: url("cat-2x.png") では 400×240 でした。

type() で AVIF や WebP を選ばせる

MDN の image-set() のページにもあるとおり、候補に type() で画像の形式(MIME タイプ)を書くと、ブラウザーが表示できる形式の候補だけが選ばれます。

css
.hero {
  background-image: image-set(
    "cat-2x.avif" type("image/avif") 2x,
    "cat-2x.webp" type("image/webp") 2x,
    "cat-2x.png" type("image/png") 2x
  );
}

検証環境の Chromium は AVIF を表示できるので、cat-2x.avif だけが読み込まれました。先頭に type("image/x-unknown") のような知らない形式の候補を置くと、その候補は飛ばされ、次の cat-2x.png が読み込まれました。仕様では、知らない形式や対応していない形式の候補を除いてから選ぶ、と定められています。

同じ倍率の候補が並ぶときは、書く順番で結果が変わります。PNG を先、AVIF をあとに(どちらも 2x で)書くと、AVIF を表示できる Chromium でも cat-2x.png が読み込まれました。仕様では、前の候補と同じ倍率の候補は除かれると定められています。そのため、あとに書いた AVIF の候補が除かれ、PNG が残ります。AVIF や WebP を使いたいときは、それらを先に、PNG や JPEG を最後に書きます。

type() と倍率は組み合わせられます。"cat-2x.avif" type("image/avif") 2x, "cat-2x.png" 2x, "cat-1x.png" 1x と書くと、倍率1では cat-1x.png、倍率2では cat-2x.avif が読み込まれました。

type() は画像の中身を変えるものではありません。仕様にも、PNG の画像に type("image/jpeg") と書いても、その候補が選ばれれば PNG がそのまま表示されると書かれています。

対応ブラウザーと -webkit-image-set()

互換性データでは、接頭辞なしの image-set() に対応したバージョンは次のとおりです。

ブラウザー image-set()
Chrome・Edge 113
Firefox 89
Safari(iOS を含む) 17

これより前のバージョンにも、一部だけ対応したものがあります。Firefox 88 と Safari 14〜16 は type() に対応していません。Chrome・Edge 112 までの -webkit-image-set()(Chrome は 20 から、Edge は 79 から)と、Safari 10.1〜13(iOS は 10.3〜13)の image-set() は、url の画像と x の単位だけに対応しています。

-webkit-image-set() は、古い書き方として残されている別名です。仕様では、CSS を解析するときに image-set() に置き換えて扱うと定められています。検証環境でも、-webkit-image-set() で書くと cat-2x.png が選ばれ、getComputedStyle() の値は接頭辞なしの image-set(...) になっていました。

image-set() を読めないブラウザーのために、前に url() の指定を置いておく書き方があります。

css
.hero {
  background-image: url("cat-1x.png");
  background-image: image-set("cat-1x.png" 1x, "cat-2x.png" 2x);
}

MDN の「CSS エラー処理」のページにあるとおり、ブラウザーは読めない値の宣言を捨てて、次の宣言に進みます。読めないブラウザーでは前の url() が残ります。検証環境の Chromium で倍率2のページを開くと、読み込まれたのは cat-2x.png だけでした。前に書いた cat-1x.png は、image-set() を読めるブラウザーではリクエストされませんでした。

​Posted by 86