CSSで、マウスを乗せると画像を枠の中で拡大する方法

​​CSS​HTML,Playwright

マウスを乗せると画像が枠の中で大きくなる動きは、画像を包む枠に overflow: hidden を付け、:hover のときに中の img を transform: scale(1.1) で拡大すると作れます。枠の大きさは変わらず、枠からはみ出した部分は切り取られるので、周りのレイアウトも動きません。overflow: hidden を付け忘れると、拡大した画像が隣の画像に重なり、クリックまで横取りします。その様子も、測った値で紹介します。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS だけのページを Playwright で開いてマウスとキーボードで操作し、getComputedStyle() と getBoundingClientRect() の値、画面を撮った画像の色、click が届いた要素を記録しました。画像は、検証のために描いた SVG を使っています。動きを減らす設定は、Playwright のエミュレーションで試しています。Firefox と Safari、スマートフォンの実機は試していません。

横に並んだ3枚の画像に、左から順にマウスを乗せる。乗せた画像が枠の中で少し大きくなり、下の「拡大率」が 1.10倍 になった瞬間だけピンクになる。次の画像へ移ると前の画像は元の大きさに戻り、最後にマウスを外へ出すと3枚目も戻る
3枚の画像に順にマウスを乗せたときの動き。拡大する動きは、見やすいように実際の2倍の時間をかけて再生している(上の表示、マウスの絵、拡大率の表示は GIF 用に足したもの)

枠に overflow: hidden、画像に transform: scale() を付ける

画像をリンクの a で包み、その a を枠にします。

html
<a class="thumb" href="/articles/1">
  <img src="photo.jpg" alt="丘の上の猫" width="400" height="300">
</a>
css
.thumb {
  display: block;
  width: 240px;
  aspect-ratio: 4 / 3;
  overflow: hidden; /* 枠からはみ出した部分を切り取る */
  border-radius: 12px;
}

.thumb img {
  display: block; /* 画像の下にできるすき間をなくす */
  width: 100%;
  height: 100%;
  object-fit: cover; /* 縦横の比率を保ったまま、枠いっぱいに切り抜く */
  transition: transform 300ms ease-out;
}

.thumb:hover img,
.thumb:focus-visible img {
  transform: scale(1.1); /* 1.1倍に拡大する */
}

枠の a は、幅 240px で、aspect-ratio で縦横の比を 4:3 にしています。高さは 180px になります。中の img は、枠いっぱいの大きさです。

マウスを乗せている間は、transform に scale() を指定して、画像を 1.1倍にします。大きくなった画像は枠からはみ出しますが、枠の overflow: hidden が、はみ出した部分を切り取ります。

Chromium で枠にマウスを乗せ、値を読み取った結果です。位置は、ページの左上からの距離です。

乗せる前 乗せている間 外した後
枠の :hover 当てはまらない 当てはまる 当てはまらない
画像の transform none matrix(1.1, 0, 0, 1.1, 0, 0) none
枠の位置と大きさ 左 16px・上 16px、240×180px 変わらない 変わらない
画像の位置と大きさ 左 16px・上 16px、240×180px 左 4px・上 7px、264×198px 左 16px・上 16px、240×180px
下の段落の上の端 212px 212px —

乗せている間、画像は 264×198px になり、左右に 12px ずつ、上下に 9px ずつ枠の外へ広がりました。拡大の起点は、画像の中心です。枠の位置と大きさは変わらず、すぐ下に置いた段落の位置も動きませんでした。transform で変わったのは画像の描かれる大きさだけで、レイアウトは乗せる前のままでした。

transform の値は、transition で 300ms かけて変えています。transition を一時停止し、60ms ずつ進めて拡大率を読み取りました。

経過時間 0ms 60ms 120ms 180ms 240ms 300ms
拡大率 1.000 1.031 1.057 1.079 1.094 1.100

ease-out なので、はじめに速く大きくなり、1.1倍に近づくほどゆっくりになります。

overflow: hidden がないと、隣の画像に重なってクリックも奪う

枠の overflow を既定の visible に戻して、同じ大きさの画像を 8px 空けて2枚並べました。下の画像は、左の画像にスクリプトで scale(1.1) を付け、マウスを乗せたときと同じ 1.1倍にして撮った画面です(マウスの絵は描き足したもの)。比べるために、overflow: hidden を付けた2枚も上に並べています。

2組の画像の比較。上の「overflow: hidden あり」では、左の画像を拡大しても、角の丸い枠の中に収まっている。下の「overflow: hidden なし」では、2枚とも角が四角く、左の画像が枠より大きく広がって、右の画像の左の端に重なっている
左の画像を、:hover のときと同じ 1.1倍にして撮った画面。上が overflow: hidden あり、下がなし(マウスの絵は描き足したもの)

overflow: hidden がないと、拡大した画像は枠からはみ出したまま表示されます。左右に 12px 広がるので、8px のすき間を越えて、右の画像の左の端に 4px 重なりました。また、下の段では、拡大していない右の画像も含めて、2枚とも角が四角いままでした。枠に付けた border-radius の丸みで中の画像の角が切り取られたのは、overflow: hidden を付けた上の段だけです。上の段の枠では、拡大している間も、枠の左上の角から 2px の点は背景の白のままでした。

重なるのは見た目だけではありません。左の画像を拡大した状態で、マウスを右の画像の左の端から 2px 内側に動かし、クリックしました。

overflow: hidden あり overflow: hidden なし
その点の色 右の画像の色 左の画像の色
その点の真下の要素 右の画像 左の画像
:hover が当てはまる枠 右の枠 左の枠のまま
click が届いたリンク 右のリンク 左のリンク

overflow: hidden がないと、右の画像の端を押しても、左のリンクとして押されました。枠の上の端より 4px 上の点も同じで、真下の要素は、枠の外なのに左の画像でした。overflow: hidden を付けた枠では、はみ出した部分は表示されず、マウスの当たる範囲も枠の中だけでした。

画像の下の 4px のすき間は display: block で消す

上の CSS で img に display: block を付けているのは、画像の下にすき間ができないようにするためです。すき間ができるのは、枠の高さを決めずに、画像の高さに合わせたときです。比べるため、aspect-ratio を使わない枠(背景は灰色)に、width: 100%; height: auto; の画像を入れました。

img の display 枠の高さ 画像の高さ 枠の下の端から 2px 上の色
指定しない(inline) 184px 180px 枠の背景の灰色
block 180px 180px 画像の色

img の display は、何も指定しないと inline です。このとき、枠は画像より 4px 高くなり、画像の下に枠の背景が見えました。display: block にすると、枠の高さは画像と同じ 180px になり、灰色のすき間は消えました。

縦長の画像は object-fit: cover で枠に合わせる

枠の比率は 4:3 に決めているので、比率の違う画像を入れることもあります。object-fit を付けないと、既定の fill になり、画像は枠いっぱいに引き伸ばされます。cover にすると、縦横の比率を保ったまま枠を埋め、はみ出した部分が切り取られます。

上から 50px だけを赤く塗った、縦 400px・横 300px の画像で確かめました。

object-fit 枠の上の端から 5px の色 枠の真ん中の色
fill(既定) 赤 青
cover 青 青

fill では、画像全体が枠に押し込まれ、上の赤い帯がつぶれて見えていました。cover では、比率を保ったまま枠の幅 240px に合わせて縮め(240×320px)、枠からはみ出した上下を切り取るので、赤い帯は枠の外に出て見えなくなりました。

右下に向かって拡大するには transform-origin を変える

拡大の起点は、transform-origin で変えられます。既定は center です。img に transform-origin: right bottom; を付けて、マウスを乗せました。

左右の端 上下の端
枠 16px〜256px 665px〜845px
乗せている間の画像 −8px〜256px 647px〜845px

画像の右の端と下の端は枠と同じ位置のままで、画像は左と上にだけ 24px・18px 広がりました。右下の角を止めたまま、左上に向かって大きくなります。

カードの見出しに乗せたときも画像を拡大する

画像の下に見出しを並べたカードでは、見出しの上にマウスがあるときも画像を拡大したいことがあります。その場合は、:hover を見る要素を、カード全体を包む a にします。

html
<a class="card" href="/articles/1">
  <div class="thumb">
    <img src="photo.jpg" alt="丘の上の猫" width="400" height="300">
  </div>
  <p>カードの見出し</p>
</a>
css
.card {
  display: block;
  width: 240px;
  color: #1f2a30;
  text-decoration: none;
}

.card:hover .thumb img,
.card:focus-visible .thumb img {
  transform: scale(1.1);
}

枠の .thumb は、最初の CSS をそのまま使います。見出しの上にマウスを乗せると、カードの a に :hover が当てはまり、画像は matrix(1.1, 0, 0, 1.1, 0, 0) になりました。枠の大きさは 240×180px のままでした。

キーボードで選んだときと、動きを減らす設定のとき

最初の CSS では、:hover と並べて :focus-visible にも同じ transform を指定しています。キーボードで操作する人にも、どの画像を選んでいるかが拡大で伝わります。

ページを開いて Tab キーを押すと、1枚目の枠に :focus-visible が当てはまり、画像は 1.1倍になりました。枠には overflow: hidden を付けていますが、ブラウザが付けるフォーカスリング(outline)は切り取られずに見えていました。枠の左の端より 1px 外側の点は、フォーカスリングの色(#101010)になっていました。

マウスで2枚目をクリックし、マウスを外に出したときは、フォーカスは2枚目にありましたが :focus-visible は当てはまらず、画像は元の大きさに戻りました。

OS で動きを減らす設定をオンにしている人には、prefers-reduced-motion の reduce が当てはまります。このときは transition をなくし、拡大していく途中の動きを見せないようにします。

css
@media (prefers-reduced-motion: reduce) {
  .thumb img {
    transition: none;
  }
}

Playwright の emulateMedia({ reducedMotion: 'reduce' }) でこの設定をオンにし、マウスを乗せました。拡大していく途中の動きはなく、画像は乗せた直後から 1.1倍になっていました。

​Posted by 86