マグネティックボタン

カーソルの位置に応じてボタンが磁石のように引き寄せられ、内部要素も視差で追従します。ヒーローのCTAやナビに使うと注目を集められます。

#js#pointer#interaction#button

ライブデモ

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