ボタン 中級

放射フィル

白地に縁取りだけのボタン。ホバーするとカーソルのある位置を起点に円状の塗りが広がり、文字色も同時に反転します。カーソルを追いかける動きが目を引くCTAに使えます。

#css#js#button#hover

ライブデモ

コード

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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。