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

ホバーでボタンの矢印が右へ抜け、左から新しい矢印が入る

デモ

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

仕様

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

しくみと調整

右端の丸に、同じ形の SVG の矢印を 2 本重ねておきます。丸には overflow: hidden を付け、はみ出した矢印を切ります。カーソルが重なったら、1 本目を右へ押し出して丸の外へ抜き、少し遅れて 2 本目を左の外から中央へ入れます。2 本は同じ形なので、入れ替わったあとの見た目は始めと区別がつきません。

1 本目は 0.3 秒の power2.in で、矢印の幅の 1.7 倍だけ右へ送ります。2 本目は 0.18 秒遅れて動き出し、0.45 秒の power3.out で中央に止まります。加速して抜け、減速して入るので、1 本の矢印が右へ飛び出して、ひと回りして戻ってきたように見えます。遅れを 0.3 秒以上にすると丸が一瞬空になり、0.1 秒以下にすると 2 本が並んで見えます。

終わりの見た目が始めと同じなので、離れたときに逆再生する必要はありません。重なるたびに頭から流すだけです。ただし、入れ替わりの途中でカーソルが出入りしたときに頭から流し直すと、抜けかけた矢印が中央へ跳んで見えます。isActive() で動いている最中かを確かめ、その間は流し直さないようにします。

キーボードで移ってきたとき(focus)にも流します。入れ替わっても見た目は変わらないので、動きを減らす設定では何もしません。矢印を下向きにして縦に入れ替えればダウンロードのボタンに、斜め上向きにすれば外のサイトへのリンクに使えます。「詳しく見る」「次へ」のようなリンクのボタンや、カードの右下の矢印に向いています。

コード

HTML
<!-- 丸の中に同じ形の矢印を 2 本重ねる。1 本目が右へ抜け、2 本目が左から入る -->
<a class="arrow-button" href="/works/">
  <span class="arrow-button__label">詳しく見る</span>
  <span class="arrow-button__icon" aria-hidden="true">
    <svg class="arrow-button__arrow" viewBox="0 0 24 24"><path d="M4 12h15M13 6l6 6-6 6" /></svg>
    <svg class="arrow-button__arrow" viewBox="0 0 24 24"><path d="M4 12h15M13 6l6 6-6 6" /></svg>
  </span>
</a>

CSS
.arrow-button {
  --ink: #eeece6;    /* ボタンの地と矢印の色 */
  --ground: #111111; /* 文字と矢印の丸の色 */
  display: inline-flex;
  align-items: center;
  gap: 16px;
  height: 48px;
  padding: 0 4px 0 24px;
  border-radius: 999px;
  background-color: var(--ink);
  color: var(--ground);
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.04em;
  text-decoration: none;
  white-space: nowrap;
}

/* 矢印の窓。はみ出した矢印は丸で切れる */
.arrow-button__icon {
  position: relative;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: var(--ground);
  overflow: hidden;
}

/* 2 本とも丸の中央に置く。2 本目を左の外へ出しておくのは script の役目 */
.arrow-button__arrow {
  position: absolute;
  inset: 0;
  width: 18px;
  height: 18px;
  margin: auto;
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.arrow-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;

document.querySelectorAll(".arrow-button").forEach((button) => {
  // 入れ替わっても見た目は始めと同じなので、動きを減らす設定では何もしない
  if (reduceMotion) return;

  const [arrowOut, arrowIn] = button.querySelectorAll(".arrow-button__arrow");

  // 1 本目が抜けきる少し前に 2 本目を入れ始めると、途切れずに入れ替わって見える
  const swap = gsap
    .timeline({ paused: true })
    .fromTo(arrowOut, { xPercent: 0 }, { xPercent: 170, duration: 0.3, ease: "power2.in" }, 0)
    .fromTo(arrowIn, { xPercent: -170 }, { xPercent: 0, duration: 0.45, ease: "power3.out" }, 0.18);

  // 終わりの見た目が始めと同じなので、離れたときに戻す動きは要らない。
  // 入れ替わりの途中で入り直したときは流し直さない(頭から流すと、抜けかけた矢印が中央へ跳ぶ)
  const run = () => {
    if (!swap.isActive()) swap.restart();
  };
  button.addEventListener("pointerenter", run);
  button.addEventListener("focus", run);
});

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

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