clip-path ホバーリビール
clip-path の circle() をマウス位置から広げ、下の画像を円形に切り抜いて見せるホバー演出。商品やビフォーアフターの差分提示に使えます。
ライブデモ
使用例(お題: カフェ MOON BREW)
この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- MOON BREW:焙煎ビフォーアフターを clip-path 円で見せる商品カード -->
<section class="mb-roast">
<div class="mb-roast__head">
<span class="mb-roast__tag">SINGLE ORIGIN</span>
<h2 class="mb-roast__title">同じ豆、ふたつの焙煎。</h2>
<p class="mb-roast__lead">カーソルを写真に重ねると、生豆から深煎りへ。<br>あなた好みの一杯を見つけて。</p>
</div>
<!-- ホバー位置から円形に深煎り写真が広がる -->
<figure class="mb-reveal" id="mbReveal" tabindex="0" aria-label="ホバーで焙煎後を表示">
<img class="mb-reveal__base" src="https://picsum.photos/720/520?random=11" alt="焙煎前の生豆">
<img class="mb-reveal__top" src="https://picsum.photos/720/520?random=12&grayscale" alt="焙煎後の豆">
<figcaption class="mb-reveal__cap">☾ HOVER で焙煎</figcaption>
<span class="mb-reveal__from">生豆 / GREEN</span>
<span class="mb-reveal__to">深煎り / DARK ROAST</span>
</figure>
</section>
CSS
/* MOON BREW:焙煎ビフォーアフター clip-path リビール */
:root {
--cream: #f5ede1;
--brown: #2b1d12;
--amber: #c98a3b;
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 400px;
display: flex;
align-items: center;
gap: 26px;
padding: 0 28px;
font-family: "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
background:
radial-gradient(120% 90% at 100% 0%, #3a2818 0%, var(--brown) 60%);
color: var(--cream);
overflow: hidden;
}
.mb-roast__head { flex: 0 0 220px; }
.mb-roast__tag {
font-size: 10px;
letter-spacing: 0.3em;
color: var(--amber);
}
.mb-roast__title {
margin: 10px 0 12px;
font-size: 24px;
line-height: 1.4;
font-family: "Hiragino Mincho ProN", "Yu Mincho", serif;
}
.mb-roast__lead {
margin: 0;
font-size: 12.5px;
line-height: 1.9;
color: rgba(245,237,225,0.78);
}
/* リビール枠 */
.mb-reveal {
position: relative;
flex: 1;
height: 300px;
margin: 0;
border-radius: 18px;
overflow: hidden;
cursor: crosshair;
box-shadow: 0 18px 40px rgba(0,0,0,0.4);
outline: none;
}
.mb-reveal__base,
.mb-reveal__top {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* 上層(深煎り)を琥珀トーンに寄せ、円形に切り抜いて出現 */
.mb-reveal__top {
filter: sepia(0.5) saturate(1.4) brightness(0.85) contrast(1.1);
clip-path: circle(0 at 50% 50%);
transition: clip-path 0.12s ease-out;
}
.mb-reveal:hover .mb-reveal__top,
.mb-reveal:focus .mb-reveal__top {
clip-path: circle(120px at var(--mx, 50%) var(--my, 50%));
}
.mb-reveal__cap {
position: absolute;
left: 14px;
bottom: 14px;
padding: 7px 14px;
border-radius: 999px;
font-size: 11px;
letter-spacing: 0.1em;
background: rgba(43,29,18,0.55);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
color: var(--cream);
}
.mb-reveal__from,
.mb-reveal__to {
position: absolute;
top: 14px;
font-size: 10px;
letter-spacing: 0.15em;
padding: 5px 11px;
border-radius: 999px;
background: rgba(0,0,0,0.5);
}
.mb-reveal__from { left: 14px; }
.mb-reveal__to { right: 14px; background: rgba(201,138,59,0.85); color: #2b1d12; font-weight: 700; }
JavaScript
// マウス位置を CSS変数へ渡し、clip-path 円の中心を追従させる
const reveal = document.getElementById("mbReveal");
if (reveal) {
const move = (clientX, clientY) => {
const r = reveal.getBoundingClientRect();
if (!r.width || !r.height) return;
// 枠内の相対位置を % で算出
const x = ((clientX - r.left) / r.width) * 100;
const y = ((clientY - r.top) / r.height) * 100;
reveal.style.setProperty("--mx", `${x}%`);
reveal.style.setProperty("--my", `${y}%`);
};
reveal.addEventListener("pointermove", (e) => move(e.clientX, e.clientY));
// キーボードフォーカス時は中央に出す
reveal.addEventListener("focus", () => {
reveal.style.setProperty("--mx", "50%");
reveal.style.setProperty("--my", "50%");
});
}
コード
HTML
<!-- clip-path で2枚の画像をホバー切り替え -->
<div class="stage">
<figure class="reveal" tabindex="0" aria-label="ホバーで第2画像を表示">
<!-- 下層: 通常時に見える画像 -->
<img class="reveal__base" src="https://picsum.photos/id/1015/800/600" alt="風景(通常)">
<!-- 上層: clip-path で出現するモノクロ画像 -->
<img class="reveal__top" src="https://picsum.photos/id/1015/800/600?grayscale" alt="風景(モノクロ)">
<figcaption class="reveal__cap">HOVER ME</figcaption>
</figure>
</div>
CSS
/* ステージ全体 */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: radial-gradient(circle at 30% 20%, #2a2d4a, #14151f 70%);
font-family: "Segoe UI", system-ui, sans-serif;
}
.stage { padding: 24px; }
/* 画像枠 */
.reveal {
position: relative;
width: min(64vw, 380px);
aspect-ratio: 4 / 3;
margin: 0;
border-radius: 16px;
overflow: hidden;
cursor: pointer;
box-shadow: 0 20px 50px -15px rgba(0, 0, 0, .6);
outline: none;
}
.reveal img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* 上層は斜めの clip-path で隠しておく */
.reveal__top {
/* マウス位置を CSS 変数で受け取り、円形に切り抜く */
clip-path: circle(0% at var(--x, 50%) var(--y, 50%));
transition: clip-path .45s cubic-bezier(.22, 1, .36, 1);
filter: saturate(1.1) contrast(1.05);
}
/* ホバー / フォーカスで広く切り抜く */
.reveal:hover .reveal__top,
.reveal:focus-visible .reveal__top {
clip-path: circle(75% at var(--x, 50%) var(--y, 50%));
}
/* キャプション */
.reveal__cap {
position: absolute;
left: 50%;
bottom: 16px;
transform: translateX(-50%);
padding: 8px 18px;
font-size: 13px;
letter-spacing: .25em;
font-weight: 700;
color: #fff;
background: rgba(20, 21, 31, .55);
backdrop-filter: blur(6px);
border-radius: 999px;
transition: opacity .3s ease;
}
.reveal:hover .reveal__cap { opacity: 0; }
/* フォーカスリング */
.reveal:focus-visible { box-shadow: 0 0 0 3px #8ab4ff, 0 20px 50px -15px rgba(0, 0, 0, .6); }
@media (prefers-reduced-motion: reduce) {
.reveal__top { transition: none; }
}
JavaScript
// マウス位置を CSS 変数(--x/--y)へ反映し、その地点から円形リビールさせる
const reveal = document.querySelector(".reveal");
if (reveal) {
// 枠内の相対位置を % で算出して変数に書き込む
const move = (clientX, clientY) => {
const r = reveal.getBoundingClientRect();
const x = ((clientX - r.left) / r.width) * 100;
const y = ((clientY - r.top) / r.height) * 100;
reveal.style.setProperty("--x", `${x.toFixed(1)}%`);
reveal.style.setProperty("--y", `${y.toFixed(1)}%`);
};
reveal.addEventListener("pointermove", (e) => move(e.clientX, e.clientY));
// キーボード操作(フォーカス)時は中央から開く
reveal.addEventListener("focus", () => {
reveal.style.setProperty("--x", "50%");
reveal.style.setProperty("--y", "50%");
});
}
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「clip-path ホバーリビール」の効果を追加してください。
# 追加してほしい効果
clip-path ホバーリビール(画像エフェクト)
clip-path の circle() をマウス位置から広げ、下の画像を円形に切り抜いて見せるホバー演出。商品やビフォーアフターの差分提示に使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- clip-path で2枚の画像をホバー切り替え -->
<div class="stage">
<figure class="reveal" tabindex="0" aria-label="ホバーで第2画像を表示">
<!-- 下層: 通常時に見える画像 -->
<img class="reveal__base" src="https://picsum.photos/id/1015/800/600" alt="風景(通常)">
<!-- 上層: clip-path で出現するモノクロ画像 -->
<img class="reveal__top" src="https://picsum.photos/id/1015/800/600?grayscale" alt="風景(モノクロ)">
<figcaption class="reveal__cap">HOVER ME</figcaption>
</figure>
</div>
【CSS】
/* ステージ全体 */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: radial-gradient(circle at 30% 20%, #2a2d4a, #14151f 70%);
font-family: "Segoe UI", system-ui, sans-serif;
}
.stage { padding: 24px; }
/* 画像枠 */
.reveal {
position: relative;
width: min(64vw, 380px);
aspect-ratio: 4 / 3;
margin: 0;
border-radius: 16px;
overflow: hidden;
cursor: pointer;
box-shadow: 0 20px 50px -15px rgba(0, 0, 0, .6);
outline: none;
}
.reveal img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* 上層は斜めの clip-path で隠しておく */
.reveal__top {
/* マウス位置を CSS 変数で受け取り、円形に切り抜く */
clip-path: circle(0% at var(--x, 50%) var(--y, 50%));
transition: clip-path .45s cubic-bezier(.22, 1, .36, 1);
filter: saturate(1.1) contrast(1.05);
}
/* ホバー / フォーカスで広く切り抜く */
.reveal:hover .reveal__top,
.reveal:focus-visible .reveal__top {
clip-path: circle(75% at var(--x, 50%) var(--y, 50%));
}
/* キャプション */
.reveal__cap {
position: absolute;
left: 50%;
bottom: 16px;
transform: translateX(-50%);
padding: 8px 18px;
font-size: 13px;
letter-spacing: .25em;
font-weight: 700;
color: #fff;
background: rgba(20, 21, 31, .55);
backdrop-filter: blur(6px);
border-radius: 999px;
transition: opacity .3s ease;
}
.reveal:hover .reveal__cap { opacity: 0; }
/* フォーカスリング */
.reveal:focus-visible { box-shadow: 0 0 0 3px #8ab4ff, 0 20px 50px -15px rgba(0, 0, 0, .6); }
@media (prefers-reduced-motion: reduce) {
.reveal__top { transition: none; }
}
【JavaScript】
// マウス位置を CSS 変数(--x/--y)へ反映し、その地点から円形リビールさせる
const reveal = document.querySelector(".reveal");
if (reveal) {
// 枠内の相対位置を % で算出して変数に書き込む
const move = (clientX, clientY) => {
const r = reveal.getBoundingClientRect();
const x = ((clientX - r.left) / r.width) * 100;
const y = ((clientY - r.top) / r.height) * 100;
reveal.style.setProperty("--x", `${x.toFixed(1)}%`);
reveal.style.setProperty("--y", `${y.toFixed(1)}%`);
};
reveal.addEventListener("pointermove", (e) => move(e.clientX, e.clientY));
// キーボード操作(フォーカス)時は中央から開く
reveal.addEventListener("focus", () => {
reveal.style.setProperty("--x", "50%");
reveal.style.setProperty("--y", "50%");
});
}
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。