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

ホバーでボタンを光が斜めに横切る

デモ

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

仕様

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

しくみと調整

ボタンの左の外に、斜めに傾けた光の帯を隠しておき、カーソルが重なったら右の外まで流します。帯は中央だけが淡いグレージュに光り、両端が透明なグラデーションで、幅はボタンの半分(50%)です。ボタンは、はみ出しを隠した丸帯なので、光は丸帯の形に切り取られて見えます。

帯は skewX で -20° 傾け、1.155 秒の power2.inOut で流します。傾けた帯は、ボタンの高さ × tan 20° だけ角が横へはみ出すので、その分だけ流し始めと流し終わりの位置を外へずらします。ずらさないと、流す前と流し終えたあとに、帯の角がボタンの端にのぞきます。

帯は文字より下に置きます。上に重ねると、帯が通るあいだ白い文字が光に溶けて読めなくなります。下に回すために z-index を -1 にするときは、ボタンに isolation の isolate を付けます。付けないと、帯がボタンの地より後ろへ回り、何も見えなくなります。

光は重なるたびに 1 回だけ流し、離れるときは何もしません。流れている途中にまた重なっても、頭から流し直さずに流しきらせます。流し直すと、帯が途中から左の外へ飛んで見えるためです。キーボードで焦点が来たときも 1 回流し、動きを減らす設定では、流さずに真ん中で淡く光らせて消すだけにします。

明るい光は暗い面の上でよく見えるので、墨色の主ボタンに向いています。光の色は白でもよく、グレージュにすると金属の照り返しのような落ち着いた光になります。明るいボタンでは帯が地に溶けるので、墨色の帯を薄く流します。重ねなくても数秒おきに流せば(repeatDelay を付けた繰り返し)、目を留めてほしいボタンになります。有料プランへの誘導や購入のボタンに向いています。

コード

HTML
<a class="shine-button" href="/pricing/">
  <span class="shine-button__shine" aria-hidden="true"></span>
  <span class="shine-button__label">プランをアップグレード</span>
</a>

CSS
.shine-button {
  --face: #262626;                    /* 地 */
  --on-face: #ffffff;                 /* 文字 */
  --shine: rgba(210, 185, 153, 0.8); /* 光の帯の芯(淡いグレージュ)。両端は透明 */
  position: relative;
  display: inline-grid;
  place-items: center;
  height: 48px;
  padding: 0 32px;
  border-radius: 999px;
  overflow: hidden; /* 光の帯を丸帯の形に切る */
  isolation: isolate; /* 帯(z-index: -1)を、ボタンの地の上・文字の下に描く */
  background-color: var(--face);
  color: var(--on-face);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-decoration: none;
  white-space: nowrap;
}

/* 光の帯。幅はボタンの 50%。ボタンの左の外に置いておき、重なったら右の外まで流す */
.shine-button__shine {
  position: absolute;
  top: 0;
  right: 100%;
  z-index: -1;
  width: 50%;
  height: 100%;
  background: linear-gradient(90deg, transparent, var(--shine), transparent);
  pointer-events: none;
}

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

JavaScript
import { gsap } from "gsap";

const SKEW = -20; // 光の帯の傾き(度)
const TILT = Math.tan((Math.abs(SKEW) * Math.PI) / 180);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

document.querySelectorAll(".shine-button").forEach((button) => {
  const shine = button.querySelector(".shine-button__shine");
  // 傾けた帯は、高さ × tan(傾き) だけ角が横へはみ出す。休みのときも、その分だけ外へずらしておく
  const slant = () => button.offsetHeight * TILT;
  gsap.set(shine, { skewX: SKEW, x: -slant() });
  let sweep = null;

  const shineOnce = () => {
    // 流れている途中は頭から流し直さない(帯が途中から左の外へ飛んで見える)
    if (sweep?.isActive()) return;
    // 大きさはその都度測る(書体が読み込まれると、ボタンの幅が変わる)
    const width = button.offsetWidth;
    const band = shine.offsetWidth;

    // 動きを減らす設定では流さず、真ん中で淡く光らせて消すだけにする
    if (reduceMotion) {
      sweep = gsap.fromTo(shine, { x: (width + band) / 2, opacity: 1 }, { opacity: 0, duration: 0.66, ease: "power1.out" });
      return;
    }

    // はみ出す分も含めて、外から外へ流す
    sweep = gsap.fromTo(shine, { x: -slant() }, { x: width + band + slant(), duration: 1.155, ease: "power2.inOut" });
  };

  // 重なるたびに 1 回だけ流す。離れるときは何もしない
  button.addEventListener("pointerenter", shineOnce);
  // キーボードの焦点のときだけ流す(マウスで押したときの焦点では流さない)
  button.addEventListener("focus", () => {
    if (button.matches(":focus-visible")) shineOnce();
  });
});

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

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