放射フィル
白地に縁取りだけのボタン。ホバーするとカーソルのある位置を起点に円状の塗りが広がり、文字色も同時に反転します。カーソルを追いかける動きが目を引くCTAに使えます。
ライブデモ
コード
HTML
<!-- 放射フィルボタン: hoverでカーソル位置から円状に塗りが広がり、文字色が反転する -->
<div class="stage">
<button class="btn-radial" type="button" aria-label="今すぐ登録する">
<span class="btn-radial__label btn-radial__label--base">今すぐ登録する</span>
<span class="btn-radial__fill" aria-hidden="true"></span>
<span class="btn-radial__label btn-radial__label--over" aria-hidden="true">今すぐ登録する</span>
</button>
<p class="hint">ボタン上でカーソルを動かすと、その位置から塗りが広がります</p>
</div>
CSS
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
overflow: hidden;
font-family: "Segoe UI", system-ui, sans-serif;
background: radial-gradient(circle at 50% 20%, #fff7ed 0%, #fdeee0 50%, #f8dfc8 100%);
}
.stage {
display: grid;
place-items: center;
gap: 22px;
}
/* --x/--y はJSがpointermoveで更新するカーソル位置(%)。既定は中央なのでキーボード操作でも自然に広がる */
.btn-radial {
--x: 50%;
--y: 50%;
position: relative;
overflow: hidden;
isolation: isolate;
border: 2px solid #e0793a;
border-radius: 14px;
padding: 19px 46px;
font-size: 16px;
font-weight: 700;
letter-spacing: .02em;
color: #e0793a;
background: #fff;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
box-shadow: 0 10px 22px rgba(224, 121, 58, .14);
transition: transform .25s cubic-bezier(.22, 1, .36, 1), box-shadow .35s ease;
/* 待機時のアイドル: 影がゆっくり呼吸してクリック可能なことを伝える */
animation: radial-idle-breathe 3.4s ease-in-out infinite;
}
@keyframes radial-idle-breathe {
0%, 100% { box-shadow: 0 10px 22px rgba(224, 121, 58, .14); }
50% { box-shadow: 0 13px 28px rgba(224, 121, 58, .26); }
}
.btn-radial__label--base {
position: relative;
}
/* fill(背景) と label--over(白文字) は同じclip-path circleで同期して広がる。
DOM順(base -> fill -> over)がそのまま描画順になるため、
fillがbaseの文字を覆い隠し、overがその上にちょうど塗られた範囲だけ現れる */
.btn-radial__fill {
position: absolute;
inset: 0;
background: #e0793a;
clip-path: circle(0% at var(--x) var(--y));
transition: clip-path .55s cubic-bezier(.22, 1, .36, 1);
pointer-events: none;
}
.btn-radial__label--over {
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: #fff;
clip-path: circle(0% at var(--x) var(--y));
transition: clip-path .55s cubic-bezier(.22, 1, .36, 1);
pointer-events: none;
}
.btn-radial:hover,
.btn-radial:focus-visible {
transform: translateY(-2px);
animation-play-state: paused;
}
.btn-radial:hover .btn-radial__fill,
.btn-radial:focus-visible .btn-radial__fill,
.btn-radial:hover .btn-radial__label--over,
.btn-radial:focus-visible .btn-radial__label--over {
clip-path: circle(150% at var(--x) var(--y));
}
.btn-radial:active {
transform: translateY(0) scale(.98);
}
.btn-radial:focus-visible {
outline: 3px solid rgba(224, 121, 58, .45);
outline-offset: 4px;
}
.hint {
margin: 0;
font-size: 13px;
color: #9a6a4a;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.btn-radial { animation: none; }
.btn-radial__fill,
.btn-radial__label--over { transition-duration: .01ms; }
}
JavaScript
// 放射フィルボタン: ポインタ位置を --x/--y に反映し、CSSのclip-path circleが広がる中心に使う
(() => {
const btn = document.querySelector('.btn-radial');
if (!btn) return; // null安全
const updatePos = (clientX, clientY) => {
const rect = btn.getBoundingClientRect();
if (!rect.width || !rect.height) return;
const x = ((clientX - rect.left) / rect.width) * 100;
const y = ((clientY - rect.top) / rect.height) * 100;
btn.style.setProperty('--x', `${x}%`);
btn.style.setProperty('--y', `${y}%`);
};
btn.addEventListener('pointerenter', (e) => updatePos(e.clientX, e.clientY));
btn.addEventListener('pointermove', (e) => updatePos(e.clientX, e.clientY));
// pointerleaveでは位置を更新しない: 抜けた瞬間の位置から次回のhoverが自然に始まる
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「放射フィル」の効果を追加してください。
# 追加してほしい効果
放射フィル(ボタン)
白地に縁取りだけのボタン。ホバーするとカーソルのある位置を起点に円状の塗りが広がり、文字色も同時に反転します。カーソルを追いかける動きが目を引くCTAに使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 放射フィルボタン: hoverでカーソル位置から円状に塗りが広がり、文字色が反転する -->
<div class="stage">
<button class="btn-radial" type="button" aria-label="今すぐ登録する">
<span class="btn-radial__label btn-radial__label--base">今すぐ登録する</span>
<span class="btn-radial__fill" aria-hidden="true"></span>
<span class="btn-radial__label btn-radial__label--over" aria-hidden="true">今すぐ登録する</span>
</button>
<p class="hint">ボタン上でカーソルを動かすと、その位置から塗りが広がります</p>
</div>
【CSS】
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
overflow: hidden;
font-family: "Segoe UI", system-ui, sans-serif;
background: radial-gradient(circle at 50% 20%, #fff7ed 0%, #fdeee0 50%, #f8dfc8 100%);
}
.stage {
display: grid;
place-items: center;
gap: 22px;
}
/* --x/--y はJSがpointermoveで更新するカーソル位置(%)。既定は中央なのでキーボード操作でも自然に広がる */
.btn-radial {
--x: 50%;
--y: 50%;
position: relative;
overflow: hidden;
isolation: isolate;
border: 2px solid #e0793a;
border-radius: 14px;
padding: 19px 46px;
font-size: 16px;
font-weight: 700;
letter-spacing: .02em;
color: #e0793a;
background: #fff;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
box-shadow: 0 10px 22px rgba(224, 121, 58, .14);
transition: transform .25s cubic-bezier(.22, 1, .36, 1), box-shadow .35s ease;
/* 待機時のアイドル: 影がゆっくり呼吸してクリック可能なことを伝える */
animation: radial-idle-breathe 3.4s ease-in-out infinite;
}
@keyframes radial-idle-breathe {
0%, 100% { box-shadow: 0 10px 22px rgba(224, 121, 58, .14); }
50% { box-shadow: 0 13px 28px rgba(224, 121, 58, .26); }
}
.btn-radial__label--base {
position: relative;
}
/* fill(背景) と label--over(白文字) は同じclip-path circleで同期して広がる。
DOM順(base -> fill -> over)がそのまま描画順になるため、
fillがbaseの文字を覆い隠し、overがその上にちょうど塗られた範囲だけ現れる */
.btn-radial__fill {
position: absolute;
inset: 0;
background: #e0793a;
clip-path: circle(0% at var(--x) var(--y));
transition: clip-path .55s cubic-bezier(.22, 1, .36, 1);
pointer-events: none;
}
.btn-radial__label--over {
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: #fff;
clip-path: circle(0% at var(--x) var(--y));
transition: clip-path .55s cubic-bezier(.22, 1, .36, 1);
pointer-events: none;
}
.btn-radial:hover,
.btn-radial:focus-visible {
transform: translateY(-2px);
animation-play-state: paused;
}
.btn-radial:hover .btn-radial__fill,
.btn-radial:focus-visible .btn-radial__fill,
.btn-radial:hover .btn-radial__label--over,
.btn-radial:focus-visible .btn-radial__label--over {
clip-path: circle(150% at var(--x) var(--y));
}
.btn-radial:active {
transform: translateY(0) scale(.98);
}
.btn-radial:focus-visible {
outline: 3px solid rgba(224, 121, 58, .45);
outline-offset: 4px;
}
.hint {
margin: 0;
font-size: 13px;
color: #9a6a4a;
text-align: center;
}
@media (prefers-reduced-motion: reduce) {
.btn-radial { animation: none; }
.btn-radial__fill,
.btn-radial__label--over { transition-duration: .01ms; }
}
【JavaScript】
// 放射フィルボタン: ポインタ位置を --x/--y に反映し、CSSのclip-path circleが広がる中心に使う
(() => {
const btn = document.querySelector('.btn-radial');
if (!btn) return; // null安全
const updatePos = (clientX, clientY) => {
const rect = btn.getBoundingClientRect();
if (!rect.width || !rect.height) return;
const x = ((clientX - rect.left) / rect.width) * 100;
const y = ((clientY - rect.top) / rect.height) * 100;
btn.style.setProperty('--x', `${x}%`);
btn.style.setProperty('--y', `${y}%`);
};
btn.addEventListener('pointerenter', (e) => updatePos(e.clientX, e.clientY));
btn.addEventListener('pointermove', (e) => updatePos(e.clientX, e.clientY));
// pointerleaveでは位置を更新しない: 抜けた瞬間の位置から次回のhoverが自然に始まる
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。