本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100
ローディング

ローディングの点が ∞ の軌跡を走る

デモ

仕様

動作
DOM
種類
レシピ
使うもの
MotionPathPlugin
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

MotionPathPlugin で、点を ∞ の形のパスに沿わせます。イーズを無しにすると、一定の速さで回り続けます。

尾は、大きさ・色・不透明度を少しずつ変えた 12 個の点を、0.028 秒ずつずらして走らせたものです(stagger の繰り返し)。色は、先頭のローズから尾の紫へ移ります。地は生成りで、∞ の軌跡は淡い溝として敷いています。

点を、中心を原点にした circle にして、パスと同じ SVG の中に置くと、位置合わせの指定なしで、パスの座標がそのまま点の位置になります。∞ は 4 本の 3 次ベジェ曲線でできていて、交わる所でも向きが途切れずにつながります。

コード

HTML
<div class="infinity-loader" role="status">
  <svg class="infinity-loader__svg" viewBox="-90 -45 180 90" aria-hidden="true">
    <!-- 中心で交わる 8 の字。4 本の 3 次ベジェで、交わる所でも向きが途切れずにつながる -->
    <path class="infinity-loader__track"
          d="M0,0 C25,-35 75,-35 75,0 C75,35 25,35 0,0 C-25,-35 -75,-35 -75,0 C-75,35 -25,35 0,0 Z" />
  </svg>
  <p class="infinity-loader__text">読み込み中</p>
</div>
CSS
.infinity-loader {
  --size: 480px;      /* ∞ の横幅 */
  --track: 7;         /* 軌跡の溝の太さ(viewBox 180×90 の中の値) */
  --head: #ff3d68;    /* 先頭の点の色 */
  --tail: #7c3aed;    /* 尾の点の色 */
  --line: rgba(38, 38, 38, 0.12); /* 軌跡の溝の色 */
  --ground: #f2f1ed;  /* 地の色 */
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  background: var(--ground);
}

/* 高さは viewBox の縦横比から決まる。点は溝からはみ出すので切らない */
.infinity-loader__svg {
  width: min(var(--size), 72vw);
  height: auto;
  overflow: visible;
}

.infinity-loader__track {
  fill: none;
  stroke: var(--line);
  stroke-width: var(--track);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* 読み上げにだけ届く文言。画面には出さない */
.infinity-loader__text {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
JavaScript
import { gsap } from "gsap";
import { MotionPathPlugin } from "gsap/MotionPathPlugin";

gsap.registerPlugin(MotionPathPlugin);

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const SVG_NS = "http://www.w3.org/2000/svg";
const LAP = 2.4;    // 1 周の時間(秒)
const COUNT = 12;   // 尾を含む点の数
const EACH = 0.028; // 点どうしの遅れ(秒)

function createLoader(root) {
  const svg = root.querySelector(".infinity-loader__svg");
  const track = root.querySelector(".infinity-loader__track");
  // 色は CSS が正。GSAP は var(--name) のままでは混ぜられないので、値にして渡す
  const styles = getComputedStyle(root);
  const head = styles.getPropertyValue("--head").trim();
  const tail = styles.getPropertyValue("--tail").trim();

  // 先頭ほど大きく濃く、尾ほど小さく薄い点
  const dots = Array.from({ length: COUNT }, (_, i) => {
    const p = i / (COUNT - 1);
    const dot = document.createElementNS(SVG_NS, "circle");
    // 中心を原点にして軌跡と同じ SVG に置くと、位置合わせの指定なしでパスの座標が点の位置になる
    dot.setAttribute("cx", "0");
    dot.setAttribute("cy", "0");
    dot.setAttribute("r", String(6.5 - 4 * p));
    dot.setAttribute("fill", gsap.utils.interpolate(head, tail, p));
    dot.setAttribute("fill-opacity", String(1 - 0.7 * p));
    return dot;
  });
  svg.append(...[...dots].reverse()); // 先頭の点がいちばん上に来るよう、逆順に重ねる

  // イーズ無しで速さを一定にすると、1 周で元の位置に戻るのでそのまま繰り返せる
  const run = gsap.to(dots, {
    motionPath: { path: track },
    duration: LAP,
    ease: "none",
    stagger: { each: EACH, repeat: -1 },
  });
  // 先頭だけが走っている立ち上がりを見せないよう、尾がそろった所から始める
  run.time(LAP);

  if (reduceMotion) {
    // 走らせず、軌跡の上に等間隔で並べて、順に明滅させる
    run.pause();
    dots.forEach((dot, i) => gsap.set(dot, { motionPath: { path: track, start: i / COUNT, end: i / COUNT } }));
    gsap.to(dots, { opacity: 0.25, duration: 0.6, repeat: -1, yoyo: true, stagger: 0.08 });
  }

  return {
    // 読み込みが終わったら呼ぶ。呼ぶまで回り続ける
    // 返ってくるタイムラインに、ページ自身の出現をつなげられる(例: loader.done().add(heroIn, "-=0.2"))
    done() {
      return gsap
        .timeline({ onComplete: () => { gsap.killTweensOf(dots); root.remove(); } })
        .to(dots, { scale: 0, duration: 0.3, ease: "back.in(2)", stagger: 0.015 })
        .to(root, { autoAlpha: 0, duration: 0.3, ease: "power1.in" }, "<0.15")
        .timeScale(reduceMotion ? 3 : 1); // 動きを減らす設定では、ほぼ一瞬で片づける
    },
  };
}

// 例: 画像や fetch の完了を待って loader.done()
export const loader = createLoader(document.querySelector(".infinity-loader"));

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

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