CSSのobject-view-boxで画像の一部を切り出す方法

​​CSS​object-fit,Playwright,画像

img に object-view-box: xywh(200px 200px 400px 200px) を付けると、画像の左上から右へ 200px、下へ 200px の位置を起点に、幅 400px・高さ 200px の範囲だけが表示されます。画像を切り抜いたファイルを別に用意したり、div で包んで overflow: hidden ではみ出しを隠したりしなくても、CSS の1行で済みます。ただし、対応しているのは Chrome と Edge などの Chromium 系のブラウザーだけです。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。800×600 の PNG 画像(200×200 のマスが横4つ・縦3つ、マスごとに色と名前 A1〜D3 が違うもの)を表示し、表示サイズと、どのマスが見えたかを測りました。Firefox と Safari では試していません。対応バージョンは互換性データ(@mdn/browser-compat-data 8.1.8)の値です。

xywh() で切り出す範囲を書く

MDN の object-view-box のページにあるとおり、値には四角形を表す関数を書きます。xywh() は、左上の位置(x と y)と、幅・高さの順に書く関数です。座標は、画像そのものの大きさ(800×600)を基準にします。

css
img {
  object-view-box: xywh(200px 200px 400px 200px);
}
上に元の画像、下に object-view-box を付けた img を並べた画面。元の画像の B2 と C2 のマスをピンクの枠で囲んであり、下の img には B2 と C2 の2マスだけが表示されている
xywh(200px 200px 400px 200px) で、元の画像のピンクの枠の範囲だけが表示されるところ

検証環境で、幅も高さも書かない img に付けると、表示サイズは 400×200 になり、見えたのは B2 と C2 の2マスだけでした。元の画像は 800×600 なので、画像の大きさではなく、切り出した範囲の大きさで img の大きさが決まっています。naturalWidth と naturalHeight は、800 と 600 のままでした。

CSS Images 5 の仕様の object-view-box の節では、範囲を決めたあとは、要素が「範囲の幅と高さを元の大きさとして持つ」ものとして扱われると定められています。縦横比も範囲の比になります。そのため、CSS で幅だけを書くと、高さは範囲の比から決まります。width: 200px を足すと、表示サイズは 200×100 になりました。

inset() と rect() でも書ける

値には xywh() のほか、inset() と rect() も書けます。どれも同じ範囲を表せます。

css
/* どれも、左上が (200px, 200px)、幅 400px・高さ 200px の範囲 */
img { object-view-box: xywh(200px 200px 400px 200px); }
img { object-view-box: inset(200px 200px 200px 200px); }
img { object-view-box: rect(200px 600px 400px 200px); }

inset() は、画像の上・右・下・左の辺から、どれだけ内側に削るかを書きます。rect() は、範囲の上・右・下・左の辺の位置を、画像の上端(上と下)と左端(右と左)からの距離で書きます。検証環境で3つの書き方の img を撮って比べると、描画はまったく同じでした。

getComputedStyle() で計算値を読むと、xywh() も rect() も inset(200px calc(100% - 600px) calc(100% - 400px) 200px) のような inset() の形になりました。CSS Shapes の仕様でも、これらの関数の計算値は、同じ範囲を表す inset() になると定められています。

inset(20%) のようなパーセントも書けます。circle(50%) や polygon() のような四角形でない形と、SVG の viewBox 属性のような 0 0 400 200 という書き方は、CSS.supports() が false になり、計算値は初期値の none のままでした。

width と height の属性を書いた img は引き伸ばされる

img に width="800" height="600" のような属性を書いている場合は注意が必要です。

検証環境で、属性を書いたまま object-view-box: xywh(200px 200px 400px 200px) を付けると、表示サイズは 800×600 のままでした。400×200 の範囲(B2 と C2)が、横に2倍、縦に3倍に引き伸ばされるので、マスは縦長に表示されます。

CSS で幅を決め、高さを auto にすると、範囲の比に戻ります。

css
img {
  object-view-box: xywh(200px 200px 400px 200px);
  width: 400px;
  height: auto;
}

属性を残したまま、この CSS を足すと、表示サイズは 400×200 になりました。

img の形と範囲の形が違うときは object-fit で決める

img に幅と高さの両方を書くと、範囲の形と img の形が合わないことがあります。そのときの表示は object-fit で決まります。仕様でも、object-fit と object-position は、画像全体ではなく切り出した範囲に対して働くと定められています。

150×150 の img を3つ縦に並べた画面。object-fit: fill では B2 と C2 が縦長に引き伸ばされ、cover では B2 と C2 の左右の端が切れ、contain では B2 と C2 の上下に B1・C1 と B3・C3 の一部が見えている
正方形の img に 2:1 の範囲を切り出し、object-fit を変えて比べたところ

検証環境で、300×300 の img に 2:1 の範囲(B2 と C2)を切り出すと、次のように表示されました(上の画像は、同じ指定を 150×150 の img で撮ったものです)。

object-fit 見えたマス
fill(初期値) B2、C2(縦に引き伸ばされる)
cover B2、C2(左右の端が切れる)
contain B1、C1、B2、C2、B3、C3
none B1、C1、B2、C2、B3、C3

contain では、範囲の上下にできるすき間に、範囲の外の B1・C1 と B3・C3 が見えました。none でも、範囲の外の同じマスが見えました。仕様では、object-view-box は画像の中身を拡大・移動して、範囲を枠に合わせて配置するものとされていて、範囲の外を消すとは定められていません。切り出した範囲だけを見せたいなら、fill のまま img の形を範囲の比に合わせるか、cover にします。

cover で切れる部分は、object-position で選べます。同じ 300×300 の img で、object-position: left にすると B2 だけが、right にすると C2 だけが見えました。

範囲を小さくすると拡大、大きくすると縮小になる

img の表示サイズを変えずに、切り出す範囲だけを小さくすると、その部分が拡大されて表示されます。幅 400px の img で、範囲を xywh(200px 200px 400px 200px) から xywh(200px 200px 200px 100px) にすると、表示サイズは 400×200 のまま、見えるマスは B2 だけになりました。200×100 の範囲が2倍に拡大されています。

範囲は画像からはみ出してもかまいません。inset(-100px) のように負の値を書くと、範囲は画像の周りに 100px ずつ広がり、幅も高さも書かない img の表示サイズは 1000×800 になりました。画像のない部分は透明で、ページの背景(検証では黒)が見えました。xywh(600px 400px 400px 400px) のように右下へはみ出す範囲では、左上に D3 のマスだけが見え、残りは透明でした。

アニメーションで動かす

object-view-box はアニメーションできます。検証環境で、下の CSS の img に zoomed クラスを付けて inset(0px) から inset(200px) に切り替えると、0.5 秒の時点の計算値は inset(100px) でした。

css
img {
  width: 400px;
  height: 300px;
  object-view-box: inset(0px);
  transition: object-view-box 1s linear;
}
img.zoomed {
  object-view-box: inset(200px);
}

inset(0px) から xywh(200px 200px 400px 200px) のように、書き方が違う値の間でも動きます。element.animate() で途中(50%)の計算値を読むと、inset(100px calc(50% - 300px) calc(50% - 200px) 100px) という途中の値になりました。仕様では、object-view-box は図形(basic shape)の値どうしとして補間すると定められています。CSS Shapes の仕様のとおり xywh() の計算値も inset() の形になるので、2つの inset() の間が計算されます。

最初の値を書かずに、初期値の none から inset(200px) に切り替えると、アニメーションは作られず、すぐに inset(200px) になりました。拡大のアニメーションに使うときは、上の例のように最初の値を inset(0px) と書いておきます。

効かない場合

object-view-box は、MDN にあるとおり、画像や動画のような置換要素に使うものです。仕様では、元の幅と高さの両方を持たない要素には効かないと定められています。

検証環境で、width と height の属性がなく viewBox だけを持つ SVG ファイルを img で表示し、inset(0 50% 0 0)(右半分を削る範囲)を付けても、左半分の橙色と右半分の水色の両方が表示されました。SVG に width="800" height="600" を書き足すと、表示サイズは 400×600 になり、左半分の橙色だけが表示されました。SVG を切り出したいときは、SVG のファイルに width と height を書いておきます。

div の背景画像にも効きません。background で画像を表示した div に object-view-box: inset(0 50% 50% 0) を書くと、計算値は入りましたが、表示は画像全体のままでした。

対応しているブラウザー

互換性データ(MDN の互換性の表のもとになっているもの)では、対応しているのは Chromium 系のブラウザーだけで、実験的な機能(experimental)とされています。

ブラウザー 対応したバージョン
Chrome 104
Edge 104
Chrome(Android) 104
Firefox 未対応
Safari 未対応(Technology Preview のみ)
Safari(iOS) 未対応

Firefox と Safari では範囲が切り出されないので、画像全体が見えても困らない場面で使うのが無難です。対応しているかどうかで指定を変えたいときは、@supports で分けます。

css
@supports (object-view-box: none) {
  img.thumb {
    object-view-box: xywh(200px 200px 400px 200px);
  }
}

検証環境の Chromium では、CSS.supports('object-view-box: none') は true で、@supports の中の指定が適用されました。

​Posted by 86