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

フォーム

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

デモ

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

仕様

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

しくみと調整

丸いハンドルが反対の端へ滑るあいだ、一瞬だけ横に伸びて、着く手前でまた丸に戻ります。同時に背景は淡いグレーからブラックへ。ただ滑るだけより、伸び縮みが加わるぶん勢いがついて見えます。

移動は0.36秒の power3.inOut。ハンドルがいちばん伸びるのは、道のりの3分の1ほど進んだところ。端から離れた位置なので、伸びたハンドルがスイッチの端からはみ出しません。伸びる量は1.28倍の幅までです。

伸ばすのに scaleX を使いたくなりますが、それだと丸い端まで横に引き伸ばされ、楕円に歪みます。そこで幅そのものを補間し、border-radius は高さの半分で固定しました。幅がいくつになっても、ハンドルの端は丸いままです。

オンとオフは、色ではなく明暗で見分けられるようにしています。白いハンドルはいつも背景より明るいので、切り替えの途中でも背景に溶けません。ハンドルの色まで反転させると、途中で背景と同じ明るさになる瞬間があり、ハンドルが一瞬消えたように見えます。

オンとオフの切り替えは、1本のタイムラインを再生するか逆再生するかだけです。切り替えの途中でもう一度押すと、その位置からそのまま引き返します。

コード

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 サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。