マグネティックボタン
カーソルの位置に応じてボタンが磁石のように引き寄せられ、内部要素も視差で追従します。ヒーローのCTAやナビに使うと注目を集められます。
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- FlowDesk: SaaSヒーロー。CTAにマグネティックボタンを主役で配置 -->
<div class="fd">
<header class="fd__top">
<span class="fd__logo"><span class="fd__mark"></span>FlowDesk</span>
<nav class="fd__nav">
<a href="#">機能</a>
<a href="#">料金</a>
<a href="#">事例</a>
</nav>
<span class="fd__login">ログイン</span>
</header>
<section class="fd__hero">
<span class="fd__badge">v3.0 リリース</span>
<h1 class="fd__title">チームの仕事を、<br>ひとつの流れに。</h1>
<p class="fd__lead">タスク・ドキュメント・対話を統合。<br>FlowDeskで進捗が止まらないチームへ。</p>
<div class="fd__actions">
<button class="magnetic" type="button" aria-label="無料で始める">
<span class="magnetic__label">無料で始める</span>
<span class="magnetic__arrow" aria-hidden="true">→</span>
</button>
<span class="fd__sub">クレジットカード不要</span>
</div>
<p class="hint">CTAにカーソルを近づけてみてください</p>
</section>
</div>
CSS
/* FlowDesk SaaS テーマ: 紺/青/白 */
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 78% 18%, rgba(79,124,255,.28) 0%, transparent 42%),
radial-gradient(circle at 12% 88%, rgba(79,124,255,.16) 0%, transparent 46%),
#0f1b34;
color: #fff;
overflow: hidden;
}
.fd {
height: 400px;
display: flex;
flex-direction: column;
padding: 0 30px;
}
/* ヘッダー */
.fd__top {
display: flex;
align-items: center;
gap: 26px;
padding: 16px 0;
font-size: 14px;
}
.fd__logo {
display: inline-flex;
align-items: center;
gap: 9px;
font-weight: 800;
letter-spacing: .01em;
font-size: 16px;
}
.fd__mark {
width: 18px;
height: 18px;
border-radius: 6px;
background: linear-gradient(135deg, #4f7cff, #8fb0ff);
box-shadow: 0 0 16px rgba(79,124,255,.6);
}
.fd__nav {
display: flex;
gap: 22px;
margin-left: 10px;
}
.fd__nav a {
color: rgba(255,255,255,.72);
text-decoration: none;
}
.fd__nav a:hover { color: #fff; }
.fd__login {
margin-left: auto;
color: rgba(255,255,255,.85);
border: 1px solid rgba(255,255,255,.22);
padding: 7px 16px;
border-radius: 999px;
}
/* ヒーロー */
.fd__hero {
flex: 1;
display: grid;
place-content: center;
text-align: center;
gap: 14px;
}
.fd__badge {
justify-self: center;
font-size: 12px;
font-weight: 700;
letter-spacing: .06em;
color: #bcd0ff;
background: rgba(79,124,255,.16);
border: 1px solid rgba(79,124,255,.4);
padding: 5px 14px;
border-radius: 999px;
}
.fd__title {
margin: 0;
font-size: 32px;
line-height: 1.25;
font-weight: 800;
letter-spacing: .01em;
}
.fd__lead {
margin: 0;
font-size: 14px;
line-height: 1.7;
color: rgba(255,255,255,.66);
}
/* アクション行 */
.fd__actions {
display: inline-flex;
align-items: center;
gap: 16px;
justify-content: center;
margin-top: 8px;
}
.fd__sub {
font-size: 12px;
color: rgba(255,255,255,.5);
}
/* 主役: マグネティックボタン */
.magnetic {
--tx: 0px;
--ty: 0px;
position: relative;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 15px 34px;
font-size: 15px;
font-weight: 700;
color: #fff;
background: linear-gradient(135deg, #4f7cff 0%, #6f96ff 100%);
border: none;
border-radius: 999px;
cursor: pointer;
transform: translate(var(--tx), var(--ty));
transition: transform .45s cubic-bezier(.22,1,.36,1), box-shadow .3s ease;
box-shadow: 0 12px 34px rgba(79,124,255,.45);
}
.magnetic:hover { box-shadow: 0 18px 46px rgba(79,124,255,.6); }
.magnetic__label,
.magnetic__arrow {
display: inline-block;
transform: translate(calc(var(--tx) * .35), calc(var(--ty) * .35));
transition: transform .45s cubic-bezier(.22,1,.36,1);
}
.magnetic__arrow { font-size: 17px; }
.hint {
margin: 6px 0 0;
font-size: 12px;
letter-spacing: .04em;
color: rgba(255,255,255,.42);
}
@media (prefers-reduced-motion: reduce) {
.magnetic { transform: none !important; transition: box-shadow .3s ease; }
.magnetic__label, .magnetic__arrow { transform: none !important; }
}
JavaScript
// FlowDesk CTA: ポインタ位置からの相対ベクトルでボタンを引き寄せる
(() => {
const btn = document.querySelector('.magnetic');
if (!btn) return; // null安全
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) return;
const STRENGTH = 0.4; // 引き寄せ強度
const onMove = (e) => {
const r = btn.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
// ボタン中心からの相対距離を移動量へ変換
btn.style.setProperty('--tx', `${(e.clientX - cx) * STRENGTH}px`);
btn.style.setProperty('--ty', `${(e.clientY - cy) * STRENGTH}px`);
};
btn.addEventListener('pointermove', onMove);
btn.addEventListener('pointerenter', onMove);
// 離れたら元位置へ滑らかに復帰
btn.addEventListener('pointerleave', () => {
btn.style.setProperty('--tx', '0px');
btn.style.setProperty('--ty', '0px');
});
})();
コード
HTML
<!-- マグネティックボタン: カーソルに引き寄せられるボタン -->
<div class="stage">
<button class="magnetic" type="button" aria-label="Get Started">
<span class="magnetic__label">Get Started</span>
<span class="magnetic__arrow" 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;
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 20% 20%, #2b2d6e 0%, transparent 45%),
radial-gradient(circle at 85% 80%, #6e2b5e 0%, transparent 45%),
#0d0e1a;
color: #fff;
overflow: hidden;
}
.stage {
display: grid;
place-items: center;
gap: 22px;
}
/* 磁石ボタン本体 */
.magnetic {
--tx: 0px;
--ty: 0px;
position: relative;
display: inline-flex;
align-items: center;
gap: 12px;
padding: 18px 40px;
font-size: 17px;
font-weight: 600;
letter-spacing: .02em;
color: #0d0e1a;
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
border: none;
border-radius: 999px;
cursor: pointer;
transform: translate(var(--tx), var(--ty));
/* JS未介入時(復帰)だけ滑らかに戻す */
transition: transform .45s cubic-bezier(.22,1,.36,1), box-shadow .3s ease;
box-shadow: 0 10px 30px rgba(168, 237, 234, .25);
}
.magnetic:hover {
box-shadow: 0 16px 40px rgba(254, 214, 227, .4);
}
/* 内側の要素はさらに弱く追従(視差) */
.magnetic__label,
.magnetic__arrow {
display: inline-block;
transform: translate(calc(var(--tx) * .35), calc(var(--ty) * .35));
transition: transform .45s cubic-bezier(.22,1,.36,1);
}
.magnetic__arrow {
font-size: 19px;
}
.hint {
margin: 0;
font-size: 13px;
letter-spacing: .04em;
color: rgba(255, 255, 255, .55);
}
/* モーション抑制設定への配慮 */
@media (prefers-reduced-motion: reduce) {
.magnetic { transform: none !important; transition: box-shadow .3s ease; }
.magnetic__label, .magnetic__arrow { transform: none !important; }
}
JavaScript
// マグネティックボタン: ポインタ位置からの相対ベクトルで引き寄せる
(() => {
const btn = document.querySelector('.magnetic');
if (!btn) return; // null安全
// モーション抑制時は何もしない
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) return;
const STRENGTH = 0.4; // 引き寄せ強度
const RADIUS = 90; // 反応する余白(px)
const onMove = (e) => {
const r = btn.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
const dx = e.clientX - cx;
const dy = e.clientY - cy;
// ボタン中心からの距離に応じて移動量を決める
btn.style.setProperty('--tx', `${dx * STRENGTH}px`);
btn.style.setProperty('--ty', `${dy * STRENGTH}px`);
};
// 反応エリアを少し広げるため、親要素でも判定
btn.addEventListener('pointermove', onMove);
btn.addEventListener('pointerenter', onMove);
// 離れたら元位置へ(CSS transitionで滑らかに復帰)
btn.addEventListener('pointerleave', () => {
btn.style.setProperty('--tx', '0px');
btn.style.setProperty('--ty', '0px');
});
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「マグネティックボタン」の効果を追加してください。
# 追加してほしい効果
マグネティックボタン(マイクロインタラクション)
カーソルの位置に応じてボタンが磁石のように引き寄せられ、内部要素も視差で追従します。ヒーローのCTAやナビに使うと注目を集められます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- マグネティックボタン: カーソルに引き寄せられるボタン -->
<div class="stage">
<button class="magnetic" type="button" aria-label="Get Started">
<span class="magnetic__label">Get Started</span>
<span class="magnetic__arrow" 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;
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
background:
radial-gradient(circle at 20% 20%, #2b2d6e 0%, transparent 45%),
radial-gradient(circle at 85% 80%, #6e2b5e 0%, transparent 45%),
#0d0e1a;
color: #fff;
overflow: hidden;
}
.stage {
display: grid;
place-items: center;
gap: 22px;
}
/* 磁石ボタン本体 */
.magnetic {
--tx: 0px;
--ty: 0px;
position: relative;
display: inline-flex;
align-items: center;
gap: 12px;
padding: 18px 40px;
font-size: 17px;
font-weight: 600;
letter-spacing: .02em;
color: #0d0e1a;
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
border: none;
border-radius: 999px;
cursor: pointer;
transform: translate(var(--tx), var(--ty));
/* JS未介入時(復帰)だけ滑らかに戻す */
transition: transform .45s cubic-bezier(.22,1,.36,1), box-shadow .3s ease;
box-shadow: 0 10px 30px rgba(168, 237, 234, .25);
}
.magnetic:hover {
box-shadow: 0 16px 40px rgba(254, 214, 227, .4);
}
/* 内側の要素はさらに弱く追従(視差) */
.magnetic__label,
.magnetic__arrow {
display: inline-block;
transform: translate(calc(var(--tx) * .35), calc(var(--ty) * .35));
transition: transform .45s cubic-bezier(.22,1,.36,1);
}
.magnetic__arrow {
font-size: 19px;
}
.hint {
margin: 0;
font-size: 13px;
letter-spacing: .04em;
color: rgba(255, 255, 255, .55);
}
/* モーション抑制設定への配慮 */
@media (prefers-reduced-motion: reduce) {
.magnetic { transform: none !important; transition: box-shadow .3s ease; }
.magnetic__label, .magnetic__arrow { transform: none !important; }
}
【JavaScript】
// マグネティックボタン: ポインタ位置からの相対ベクトルで引き寄せる
(() => {
const btn = document.querySelector('.magnetic');
if (!btn) return; // null安全
// モーション抑制時は何もしない
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) return;
const STRENGTH = 0.4; // 引き寄せ強度
const RADIUS = 90; // 反応する余白(px)
const onMove = (e) => {
const r = btn.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
const dx = e.clientX - cx;
const dy = e.clientY - cy;
// ボタン中心からの距離に応じて移動量を決める
btn.style.setProperty('--tx', `${dx * STRENGTH}px`);
btn.style.setProperty('--ty', `${dy * STRENGTH}px`);
};
// 反応エリアを少し広げるため、親要素でも判定
btn.addEventListener('pointermove', onMove);
btn.addEventListener('pointerenter', onMove);
// 離れたら元位置へ(CSS transitionで滑らかに復帰)
btn.addEventListener('pointerleave', () => {
btn.style.setProperty('--tx', '0px');
btn.style.setProperty('--ty', '0px');
});
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。