



WithCodeMedia-1-pc
WithCodeMedia-2-pc
WithCodeMedia-3-pc
WithCodeMedia-4-pc




WithCodeMedia-1-sp
WithCodeMedia-2-sp
WithCodeMedia-3-sp
WithCodeMedia-4-sp









生徒サムネイル画像にマウスを乗せたらふわっと拡大する演出、どう作るのが正解ですか?
ペン博士親要素をoverflow:hiddenにして、画像側をtransform:scale()で拡大するのが定番だよ。今日はキャプション表示やグレースケール解除も一緒に紹介するね。
ポートフォリオサイトやブログのサムネイル画像にホバーで拡大エフェクトを付けたい——そう思ったとき、JavaScriptは不要です。CSSのtransform: scale()とoverflow: hiddenを組み合わせるだけで、クリーンでパフォーマンスの良いズームエフェクトが実装できます。
この記事では基本のズームから始め、暗転オーバーレイとキャプション表示の組み合わせ、filterによるモノクロ→カラー変換、カード全体ホバーで画像だけ動かす手法まで、そのままコピペして使えるコードを段階的に解説します。
アクセシビリティ面でキーボードフォーカス時にも同じ演出を発火させる実装も含めて紹介するので、実務でそのまま使えます。

画像のホバーズームを実装する前に、仕組みを理解しておきましょう。ポイントは2つです。
| プロパティ | 役割 | 設定する要素 |
|---|---|---|
| overflow: hidden | はみ出た画像を隠す | 親のdiv(ラッパー) |
| transform: scale(1.1) | 画像を110%に拡大 | imgタグ |
| transition: transform 0.3s | なめらかに変化させる | imgタグ |
| display: block | 画像下の余白をなくす | imgタグ |
imgにwidth:100%とheight:100%を指定してobject-fit:coverを使うことで、親要素のサイズに合わせて画像が切り抜かれ、比率が崩れません。これが現代的なホバーズーム実装の基本形です。

<div class="zoom-wrap">
<img src="image.jpg" alt="サンプル画像" width="400" height="300">
</div>
.zoom-wrap {
overflow: hidden;
width: 400px;
height: 300px;
}
.zoom-wrap img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.zoom-wrap:hover img {
transform: scale(1.1);
}
これだけで動作します。scale(1.1)は110%への拡大です。値が大きいほど拡大幅が増します。transition の 0.3s を 0.6s にすればゆっくり、0.15s にすればキレのある動きになります。
/* 標準的なズーム */
.zoom-default img {
transition: transform 0.3s ease;
}
/* ゆっくり始まってゆっくり終わる・上品な印象 */
.zoom-smooth img {
transition: transform 0.5s ease-in-out;
}
/* カスタムイージング:最初に少し戻ってから拡大するスプリング風 */
.zoom-spring img {
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* 急に始まってゆっくり終わる・勢いのある印象 */
.zoom-bold img {
transition: transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.zoom-default:hover img,
.zoom-smooth:hover img,
.zoom-spring:hover img,
.zoom-bold:hover img {
transform: scale(1.12);
}
cubic-bezier(0.34, 1.56, 0.64, 1)はバウンス感のあるスプリングアニメーションで、おしゃれなポートフォリオサイトでよく使われる動きです。
ポートフォリオやギャラリーでよく見るパターンです。ホバーすると画像が暗くなり、中央にテキストが現れます。
<div class="card-zoom">
<img src="image.jpg" alt="作品名" width="400" height="300">
<div class="card-overlay">
<p class="card-caption">作品タイトル</p>
<span class="card-tag">Web Design</span>
</div>
</div>
.card-zoom {
position: relative;
overflow: hidden;
width: 400px;
height: 300px;
border-radius: 8px;
}
.card-zoom img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
/* オーバーレイ(初期状態は透明) */
.card-overlay {
position: absolute;
inset: 0; /* top:0 right:0 bottom:0 left:0 の短縮 */
background: rgba(0, 0, 0, 0);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
transition: background 0.4s ease;
}
/* キャプション(初期状態は非表示) */
.card-caption {
color: #fff;
font-size: 1.25rem;
font-weight: 700;
margin: 0;
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease 0.1s, transform 0.3s ease 0.1s;
}
.card-tag {
color: rgba(255,255,255,0.8);
font-size: 0.85rem;
letter-spacing: 0.1em;
text-transform: uppercase;
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease 0.15s, transform 0.3s ease 0.15s;
}
/* ホバー時 */
.card-zoom:hover img {
transform: scale(1.08);
}
.card-zoom:hover .card-overlay {
background: rgba(0, 0, 0, 0.55);
}
.card-zoom:hover .card-caption,
.card-zoom:hover .card-tag {
opacity: 1;
transform: translateY(0);
}
キャプションにtransition-delayを設定してテキストが少し遅れて現れるようにすると、演出がより洗練されます。.card-tagのdelayを0.15sにしてズレを作っています。
<div class="card-slide">
<img src="image.jpg" alt="作品名" width="400" height="300">
<div class="slide-overlay">
<p>詳しく見る →</p>
</div>
</div>
.card-slide {
position: relative;
overflow: hidden;
width: 400px;
height: 300px;
}
.card-slide img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
.slide-overlay {
position: absolute;
inset: 0;
background: rgba(209, 97, 118, 0.85);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 1.1rem;
/* 左側から外れた位置(translateX(-100%))に初期配置 */
transform: translateX(-100%);
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.card-slide:hover img {
transform: scale(1.05);
}
.card-slide:hover .slide-overlay {
transform: translateX(0);
}
デフォルトをモノクロにしてホバーでカラーに変化させるエフェクトは、ポートフォリオサイトで特に人気のある演出です。filter: grayscale()を使います。
<div class="grayscale-wrap">
<img src="image.jpg" alt="作品" width="400" height="300">
</div>
.grayscale-wrap {
overflow: hidden;
width: 400px;
height: 300px;
}
.grayscale-wrap img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
/* デフォルトはモノクロ+少し暗め */
filter: grayscale(100%) brightness(0.85);
transition:
filter 0.4s ease,
transform 0.4s ease;
}
.grayscale-wrap:hover img {
filter: grayscale(0%) brightness(1);
transform: scale(1.05);
}
.sepia-wrap img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
filter: sepia(80%) contrast(0.9) brightness(0.9);
transition: filter 0.5s ease, transform 0.5s ease;
}
.sepia-wrap:hover img {
filter: sepia(0%) contrast(1) brightness(1);
transform: scale(1.06);
}
ホバー前にぼかし、ホバーでシャープになる演出です。
.blur-focus-wrap {
overflow: hidden;
width: 400px;
height: 300px;
}
.blur-focus-wrap img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
filter: blur(4px) brightness(0.8);
transform: scale(1.08); /* ぼかしでエッジが切れるためあらかじめ大きく */
transition: filter 0.4s ease, transform 0.4s ease;
}
.blur-focus-wrap:hover img {
filter: blur(0px) brightness(1);
transform: scale(1.0);
}
ぼかしを使う際は、あらかじめ画像をscale(1.08)程度で拡大しておかないと、blur処理でエッジ付近が白くなります。ホバー時にscale(1.0)に戻すことで自然なフォーカス感が生まれます。
よくあるブログカードやポートフォリオグリッドでは、カード全体がクリッカブルで、カードにホバーしたときに画像だけがズームするパターンが使いやすいです。
<a href="/post/1" class="blog-card">
<div class="blog-card-thumb">
<img src="thumbnail.jpg" alt="記事サムネイル" width="400" height="225">
</div>
<div class="blog-card-body">
<span class="blog-card-cat">CSS</span>
<h2 class="blog-card-title">記事タイトルがここに入ります</h2>
<p class="blog-card-date">2026年7月28日</p>
</div>
</a>
.blog-card {
display: block;
text-decoration: none;
color: inherit;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
transition: box-shadow 0.3s ease, transform 0.3s ease;
background: #fff;
}
/* カードごとわずかに浮き上がる */
.blog-card:hover {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
transform: translateY(-4px);
}
.blog-card-thumb {
overflow: hidden;
aspect-ratio: 16 / 9;
}
.blog-card-thumb img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
/* カードホバー時に画像だけズーム */
.blog-card:hover .blog-card-thumb img {
transform: scale(1.08);
}
.blog-card-body {
padding: 16px 20px 20px;
}
.blog-card-cat {
display: inline-block;
background: #d16176;
color: #fff;
font-size: 0.75rem;
padding: 3px 10px;
border-radius: 4px;
margin-bottom: 8px;
}
.blog-card-title {
font-size: 1rem;
font-weight: 700;
line-height: 1.5;
margin: 0 0 8px;
}
.blog-card-date {
font-size: 0.8rem;
color: #888;
margin: 0;
}
<div class="card-grid">
<a href="/post/1" class="blog-card"><!-- 上記と同じ構造 --></a>
<a href="/post/2" class="blog-card"></a>
<a href="/post/3" class="blog-card"></a>
</div>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
}
@media (max-width: 640px) {
.card-grid {
grid-template-columns: 1fr;
gap: 16px;
}
}

マウス操作ユーザーだけでなく、キーボードユーザーや支援技術ユーザーにも同じ体験を提供することが重要です。:focus-visibleを使ってホバーと同じスタイルをフォーカス時にも適用します。
/* hoverとfocus-visibleを並べて書く */
.zoom-wrap:hover img,
.zoom-wrap:focus-visible img {
transform: scale(1.1);
}
/* カードの場合 */
.blog-card:hover,
.blog-card:focus-visible {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
transform: translateY(-4px);
}
.blog-card:hover .blog-card-thumb img,
.blog-card:focus-visible .blog-card-thumb img {
transform: scale(1.08);
}
/* フォーカスリングをカスタマイズ(デフォルトのoutlineを上書き) */
.blog-card:focus-visible {
outline: 3px solid #d16176;
outline-offset: 2px;
}
/* オーバーレイキャプションにも対応 */
.card-zoom:hover .card-overlay,
.card-zoom:focus-visible .card-overlay {
background: rgba(0, 0, 0, 0.55);
}
.card-zoom:hover .card-caption,
.card-zoom:focus-visible .card-caption,
.card-zoom:hover .card-tag,
.card-zoom:focus-visible .card-tag {
opacity: 1;
transform: translateY(0);
}
:focusではなく:focus-visibleを使うのがポイントです。:focusはクリック時にもフォーカスリングを表示しますが、:focus-visibleはキーボード操作時のみ表示するためUXが向上します。
/* 動きを減らす設定がONの場合はズームしない */
@media (prefers-reduced-motion: reduce) {
.zoom-wrap img,
.card-zoom img,
.blog-card-thumb img {
transition: none;
}
.zoom-wrap:hover img,
.card-zoom:hover img,
.blog-card:hover .blog-card-thumb img {
transform: none; /* ズームしない */
}
/* ズームの代わりにopacityで変化を伝える */
.zoom-wrap:hover img {
opacity: 0.85;
}
}

| 問題 | 原因 | 対処法 |
|---|---|---|
| 画像が枠の外にはみ出す | overflow:hiddenが未設定 | ラッパーdivにoverflow:hiddenを付ける |
| 画像が縦に引き伸びる | object-fit:coverが未設定 | imgにobject-fit:coverを付ける |
| ズームがカクつく | transform以外をアニメーション | transformとopacityのみ使う |
| hover時に画像がぼやける | Safari+小数点scale | scale値を整数に近い値に丸める |
| キャプションが見えない | z-indexが不足 | overlayにz-index:1を付ける |
| transition が効かない | display:noneからの切り替え | opacityとvisibilityを使う |
キャプションを非表示にするとき、display: noneにするとtransitionが効きません。opacity: 0とvisibility: hiddenの組み合わせを使います。
/* NG: transitionが効かない */
.caption-ng {
display: none;
}
.wrap:hover .caption-ng {
display: block; /* transitionが効かない */
}
/* OK: opacityとvisibilityで制御 */
.caption-ok {
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.wrap:hover .caption-ok {
opacity: 1;
visibility: visible;
}
画像ホバーズームは、親をoverflow:hiddenにして中の画像をtransform:scaleで拡大するのが基本です。ポイントは①拡大はscaleでtransitionは0.3s前後②キャプションはオーバーレイで重ねる③スマホはhoverが効かないのでfocusやタップも考慮、の3点。やりすぎず上品な拡大率(1.05〜1.1)が見やすくおすすめです。
transform: scaleはGPUで処理されるためメインスレッドへの負荷がなく、60fpsのスムーズなアニメーションを維持できます。一方、widthやheightを変更すると再レイアウト(reflow)が発生し、他の要素の位置も再計算されるためパフォーマンスへの影響が大きくなります。ホバーズームには必ずtransform: scaleを使ってください。
一般的にscale(1.05)〜scale(1.15)の範囲が自然です。scale(1.05)は控えめで上品な印象、scale(1.1)が標準的、scale(1.15)以上はインパクトが強くなります。サムネイル画像には1.08〜1.1、ヒーロー画像やフルスクリーン背景には1.05程度が適しています。
SafariはサブピクセルのtransformをCPUレンダリングする場合があり、小数点の拡大率でぼやけが生じることがあります。transform: translateZ(0)をimgタグに追加してGPUレイヤーに強制昇格させるか、backface-visibility: hiddenを付与することで改善するケースがあります。
使えます。Next.jsのnext/imageはimgタグをラップするdivにoverflowが設定されていることがあるので、スタイルを上書きする際は親要素のクラスを確認してください。WordPressのアイキャッチはpost-thumbnailクラスが付くimgタグに対して.blog-card:hover .post-thumbnail imgのようにセレクターを書けば同様に動作します。
タッチデバイスのホバー動作は不安定です。タップ時に一瞬hoverが発火するブラウザもあれば、まったく効かないものもあります。スマートフォンでは「タップしたら拡大」の演出は不自然に見えることがあるので、@media (hover: hover)メディアクエリで「真のホバー可能デバイスにのみ適用する」と安全です。例:@media (hover: hover) { .zoom-wrap:hover img { transform: scale(1.1); } }
CSSの実装テクニックを「レイアウト・装飾・アニメーション・基礎・トラブル解決」の目的別に探せる総まとめページを用意しています。実装で迷ったときの索引としてどうぞ。

目的に合わせて選べる「AI副業」「AI転職」「AI活用」の3コースを用意。副収入・キャリアチェンジ・日常の生産性アップまで、あなたのゴールに合わせてAIを学べます。
会員登録はカンタン30秒で完了します。まずは公式LINEから、無料でAI学習をスタートしましょう!
WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。
公式サイト より
今すぐ
無料カウンセリング
を予約!