CSSで、マウスを乗せると画像を枠の中で拡大する方法
マウスを乗せると画像が枠の中で大きくなる動きは、画像を包む枠に 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、スマートフォンの実機は試していません。

枠に overflow: hidden、画像に transform: scale() を付ける
画像をリンクの a で包み、その a を枠にします。
<a class="thumb" href="/articles/1">
<img src="photo.jpg" alt="丘の上の猫" width="400" height="300">
</a>
.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枚も上に並べています。

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 にします。
<a class="card" href="/articles/1">
<div class="thumb">
<img src="photo.jpg" alt="丘の上の猫" width="400" height="300">
</div>
<p>カードの見出し</p>
</a>
.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 をなくし、拡大していく途中の動きを見せないようにします。
@media (prefers-reduced-motion: reduce) {
.thumb img {
transition: none;
}
}
Playwright の emulateMedia({ reducedMotion: 'reduce' }) でこの設定をオンにし、マウスを乗せました。拡大していく途中の動きはなく、画像は乗せた直後から 1.1倍になっていました。





