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

ホバーでボタンの枠線がぐるりと描かれる

デモ

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

仕様

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

しくみと調整

ボタンの上に、丸帯の形をした SVG のパスを重ねておき、カーソルが重なったら DrawSVGPlugin で線を 0% から 100% まで描きます。線は上の辺の左端から時計回りに 1 周し、半分ほど回ったところで、文字も補足の灰色から明るい文字の色へ変わります。離れると同じタイムラインを逆再生して、線を巻き戻します。

線は 0.9 秒の power2.inOut で描き、文字の色は 0.45 秒後から 0.3 秒かけて変えます。待機中の枠は補足の色の 1px、描く線は文字の色の 2px です。細い枠を下に敷いておくと、描く前からボタンに見え、描いた線との明るさの差でボタンが浮かび上がります。

丸帯を <rect> の角丸で書くと、DrawSVGPlugin は長さを角丸なしで測るので、描き終わりの位置が合いません。輪郭は <path> で書きます。パスの書き始めが線の出発点になるので、どこから描き始めるかもパスの順番で決まります。ボタンの幅は文字と書体で決まるため、パスは幅を測ってから作り、大きさが変わったら作り直して長さを測り直します。

巻き戻さずに先へ抜けさせるなら、離れたときに線の始点を 100% まで送ります。キーボードで移ってきたとき(focus)にも描き、動きを減らす設定では、ほぼ一瞬で切り替えます。暗い地のゴーストボタンや、制作実績・ポートフォリオへ誘うボタンに向いています。

コード

HTML
<!-- 枠線を描く SVG は、ボタンの大きさに合わせて script が形を入れる -->
<a class="draw-button" href="/works/">
  <svg class="draw-button__line" aria-hidden="true"><path /></svg>
  <span class="draw-button__label">制作実績を見る</span>
</a>

CSS
.draw-button {
  --ink: #eeece6;  /* 描く線と、描いたあとの文字の色 */
  --mute: #898989; /* 待機中の枠と文字の色 */
  position: relative;
  display: inline-grid;
  place-items: center;
  height: 48px;
  padding: 0 32px;
  border-radius: 999px;
  /* 待機中の細い枠。描く前からボタンに見え、描いた線との差で明るく浮かぶ */
  box-shadow: inset 0 0 0 1px var(--mute);
  color: var(--mute);
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.04em;
  text-decoration: none;
  white-space: nowrap;
}

/* 描く線。ボタンと同じ大きさで重ね、線の太さの半分だけ内側を通す */
.draw-button__line {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  fill: none;
  stroke: var(--ink);
  stroke-width: 2;
}

.draw-button__label {
  position: relative;
}

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

JavaScript
import { gsap } from "gsap";
import { DrawSVGPlugin } from "gsap/DrawSVGPlugin";

gsap.registerPlugin(DrawSVGPlugin);

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

// 丸帯の輪郭。上の辺の左端から時計回りに 1 周する(線はこの向きに描かれる)。
// <rect rx> で書くと、DrawSVGPlugin が角丸なしの長さで測るので、描き終わりが合わない
function pillPath(w, h, inset) {
  const r = h / 2 - inset;
  const x0 = inset + r;
  const x1 = w - inset - r;
  return `M${x0} ${inset} H${x1} A${r} ${r} 0 0 1 ${x1} ${h - inset} H${x0} A${r} ${r} 0 0 1 ${x0} ${inset}`;
}

document.querySelectorAll(".draw-button").forEach((button) => {
  const svg = button.querySelector(".draw-button__line");
  const line = svg.querySelector("path");
  const label = button.querySelector(".draw-button__label");
  const style = getComputedStyle(button);
  const [ink, mute] = ["--ink", "--mute"].map((name) => style.getPropertyValue(name).trim());
  let hover = null;

  // ボタンの幅は文字と書体で決まる。大きさが変わるたびに輪郭を作り直し、線の長さを測り直す。
  // viewBox を付けないので、座標は画面の px のまま(幅の端数も offsetWidth と違って丸めない)
  const build = () => {
    const { width: w, height: h } = getComputedStyle(svg);
    const inset = Number.parseFloat(getComputedStyle(line).strokeWidth) / 2;
    line.setAttribute("d", pillPath(Number.parseFloat(w), Number.parseFloat(h), inset));

    const progress = hover ? hover.progress() : 0;
    hover?.kill();
    hover = gsap
      .timeline({ paused: true })
      .fromTo(line, { drawSVG: "0%" }, { drawSVG: "100%", duration: 0.9, ease: "power2.inOut" }, 0)
      // 線が半分ほど回ったところで、文字も明るくする
      .fromTo(label, { color: mute }, { color: ink, duration: 0.3, ease: "none" }, 0.45)
      .progress(progress);
    // 動きを減らす設定では、ほぼ一瞬で切り替える
    if (reduceMotion) hover.timeScale(20);
  };
  new ResizeObserver(build).observe(button);

  const draw = () => hover?.play();
  const rewind = () => hover?.reverse();
  button.addEventListener("pointerenter", draw);
  button.addEventListener("pointerleave", rewind);
  button.addEventListener("focus", draw);
  button.addEventListener("blur", rewind);
});

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

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