本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100

ボタン

ボタンがカーソルに吸い寄せられる

デモ

操作:カーソルを動かす。 「さわってみる」を押すと自動再生が止まり、マウスで動かせます。

仕様

用途
ボタン
動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

ポインタを近づけると、触れる前からボタンがそちらへ寄ってきて、中の文字はさらに一歩先へずれます。ボタンはひと回り大きい透明な要素で包んであり、その中でポインタが動くと、中心からポインタまでの距離の0.4倍だけボタンを、0.15倍だけ文字を同じ向きへずらしています。文字はボタンと一緒に動いたうえでさらに進むので、ボタンの上に少し浮いて見えます。

包む要素は、ボタンの外側へ高さの0.6倍だけ広げました。ボタンとぴったり同じ大きさだと、ポインタが触れた瞬間にいきなり寄ってきます。周りに余白があれば、近づいていく間に少しずつ寄り始めます。追従は0.5秒の power3.out で、外へ出たら elastic.out で中央へ戻すので、少し行き過ぎてから揺り戻します。

ポインタが動くたびに新しいトゥイーンを作るので、overwrite を「auto」にして、同じ値を動かしている古いトゥイーンを止めています。これを忘れると、重なったトゥイーンが同じ位置を取り合い、ボタンが手の動きから遅れて重たくついてくるようになります。タッチ端末では画面に触れずにポインタを動かせないので、ホバーができる環境でだけ有効にしています。

ボタンの比率を上げるほどポインタの近くまで寄り、文字の比率を上げるほど奥行きが強く出ます。この動きが映えるのは、ヒーローのメインボタンやフッターの「お問い合わせ」のように、1つだけ置いて目を引きたいボタンです。並んだボタンのすべてに付けると、ポインタが通るたびにどれもが反応して、画面が落ち着きません。

コード

HTML
<!-- 外側の要素が吸い寄せの領域。ボタンより広いので、近づいた時点で反応できる -->
<div class="magnetic">
  <a class="magnetic__button" href="/contact/">
    <span class="magnetic__label">お問い合わせ</span>
  </a>
</div>

CSS
.magnetic {
  /* ボタンの外へ広げる量。ボタンの高さの 0.6 倍ほどにすると、自然に近づける */
  --zone: 28px;
  display: inline-block;
  padding: var(--zone);
}

.magnetic__button {
  --ink: #eeece6;    /* 背景色 */
  --ground: #111111; /* 文字の色 */
  display: inline-grid;
  place-items: center;
  height: 48px;
  padding: 0 32px;
  border-radius: 999px;
  background-color: var(--ink);
  color: var(--ground);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-decoration: none;
  white-space: nowrap;
}

.magnetic__label {
  display: block;
}

.magnetic__button:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}

JavaScript
import { gsap } from "gsap";

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const canHover = window.matchMedia("(hover: hover)").matches;

const PULL = 0.4; // ボタンが寄る割合(領域の中心からの距離に掛ける)
const LABEL_PULL = 0.15; // 文字がボタンの中でさらに寄る割合。ボタンより先へ行くので奥行きが出る

document.querySelectorAll(".magnetic").forEach((area) => {
  const button = area.querySelector(".magnetic__button");
  const label = area.querySelector(".magnetic__label");

  // 寄せる相手のいないタッチ端末と、動きを減らす設定では、つながない
  if (!canHover || reduceMotion) return;

  const release = () => {
    gsap.to([button, label], { x: 0, y: 0, duration: 1.1, ease: "elastic.out(1, 0.35)", overwrite: "auto" });
  };

  const follow = (event) => {
    const rect = area.getBoundingClientRect();
    const dx = event.clientX - (rect.left + rect.width / 2);
    const dy = event.clientY - (rect.top + rect.height / 2);
    // overwrite: "auto" が無いと、動かすたびにトゥイーンが積み重なって、追従が重くなる
    gsap.to(button, { x: dx * PULL, y: dy * PULL, duration: 0.5, ease: "power3.out", overwrite: "auto" });
    gsap.to(label, { x: dx * LABEL_PULL, y: dy * LABEL_PULL, duration: 0.5, ease: "power3.out", overwrite: "auto" });
  };

  area.addEventListener("pointermove", follow);
  area.addEventListener("pointerleave", release);
  // キーボードで来たときは中央へ戻す(寄ったままだと、フォーカスの輪がボタンからずれて見える)
  button.addEventListener("focus", release);
  button.addEventListener("blur", release);
});

インタラクションデザインについて、
お気軽にご相談ください。

ここに並べているのは、Web サイトの中で動く UI のサンプルです。新しく作るサイトに組み込むことも、いま公開中のサイトに、ボタンやヒーロー、スクロールの演出だけを足すこともできます。設計から実装までの進め方はWeb サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。