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

トグルスイッチが切り替わる

デモ

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

仕様

用途
フォーム
動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

つまみを端から端へ滑らせながら、地の色を淡い灰から墨色へ変えます。つまみは動き出すと横に伸び、着く前にまた丸へ戻ります。伸び縮みがあるだけで、ただ滑るより勢いがついて見えます。

つまみの移動は 0.36 秒の power3.inOut、地の色は 0.3 秒で切り替わります。伸びは 0.16 秒で 1.28 倍まで、そこから 0.2 秒かけて戻します。いちばん伸びるのが道のりの 3 分の 1 ほどの位置なので、伸びたつまみが枠の端からはみ出しません。

伸ばすのに scaleX を使うと、丸い端まで一緒に引き伸ばされて楕円に歪みます。幅そのものを補間し、border-radius を高さの半分で固定しておくと、どの幅でも端は丸いままです。

オンであることは、色ではなく明暗で伝えます。白いつまみをいつも地より明るくしておけば、切り替えの途中でもつまみが地に溶けません。つまみまで反転させると、途中で地と同じ明るさになり、一瞬つまみが消えて見えます。

オンとオフは 1 本のタイムラインの再生と逆再生なので、途中で押し返してもその位置から引き返します。押す部品を button にしておけば Enter とスペースでも切り替わり、状態は aria-checked が伝えます。設定画面、通知の受け取り、月額と年額の切り替えに向いています。

コード

HTML
<!-- その場で状態が変わるので button。状態は aria-checked、名前は中の文言が持つ -->
<button class="switch" type="button" role="switch" aria-checked="false">
  <span class="switch__label">通知を受け取る</span>
  <span class="switch__track" aria-hidden="true">
    <span class="switch__thumb"></span>
  </span>
</button>
CSS
.switch {
  --ink: #262626;                /* オンの地の色 */
  --off: rgba(38, 38, 38, 0.12); /* オフの地の色(罫線の色) */
  --knob: #ffffff;               /* つまみの色。いつも地より明るくしておく */
  --track-width: 44px;
  --track-height: 24px;
  --pad: 3px;                    /* つまみと枠のすきま */
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.switch__label {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.switch__track {
  position: relative;
  flex: none;
  width: var(--track-width);
  height: var(--track-height);
  border-radius: calc(var(--track-height) / 2);
  background-color: var(--off);
}

/* 角丸を高さの半分で固定しておくと、幅を変えても端が丸いままになる */
.switch__thumb {
  position: absolute;
  left: var(--pad);
  top: var(--pad);
  width: calc(var(--track-height) - var(--pad) * 2);
  height: calc(var(--track-height) - var(--pad) * 2);
  border-radius: calc(var(--track-height) / 2);
  background-color: var(--knob);
}

.switch:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
  border-radius: 4px;
}
JavaScript
import { gsap } from "gsap";

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

document.querySelectorAll(".switch").forEach((button) => {
  const track = button.querySelector(".switch__track");
  const thumb = button.querySelector(".switch__thumb");
  const styles = getComputedStyle(button);
  // 色は CSS が正。GSAP は var(--name) のままでは補間できないので、値にして渡す
  const off = styles.getPropertyValue("--off").trim();
  const on = styles.getPropertyValue("--ink").trim();
  // 寸法も CSS が正。組み上がった大きさから、動く量と伸びた幅を出す
  const knob = thumb.offsetWidth;
  const travel = track.offsetWidth - track.offsetHeight;
  const stretched = Math.round(knob * 1.28);

  const toggle = gsap
    .timeline({ paused: true })
    .fromTo(track, { backgroundColor: off }, { backgroundColor: on, duration: 0.3, ease: "none" }, 0)
    .fromTo(thumb, { x: 0 }, { x: travel, duration: 0.36, ease: "power3.inOut" }, 0)
    // scaleX で伸ばすと丸い端まで引き伸ばされて楕円に歪むので、幅そのものを補間する
    .fromTo(thumb, { width: knob }, { width: stretched, duration: 0.16, ease: "power2.out" }, 0)
    .fromTo(thumb, { width: stretched }, { width: knob, duration: 0.2, ease: "power2.in", immediateRender: false }, 0.16);

  // 動きを減らす設定では、ほぼ一瞬で切り替える
  if (reduceMotion) toggle.timeScale(20);

  // 最初からオンの場合(設定を読み込んだときなど)は、動かさずに終わりの状態にする
  if (button.getAttribute("aria-checked") === "true") toggle.progress(1);

  // button なので、Enter とスペースでも同じ click が起きる
  button.addEventListener("click", () => {
    const next = button.getAttribute("aria-checked") !== "true";
    button.setAttribute("aria-checked", String(next));
    next ? toggle.play() : toggle.reverse();
  });
});

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

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