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

ローディング中に点が円周を追いかけ合う

デモ

仕様

動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

中心で回る透明な腕を 4 本作り、それぞれの先に点を 1 つ付けます。腕を 0 度から 360 度まで回すと、先端の点が円周をなぞって動きます。sine.inOut を使うと上のほうでは遅く、下のほうでは速く回るので、上では点どうしが詰まり、下では間が開いて追いかけっこのように見えます。

4 本の腕は 0.14 秒ずつずらして回し始め、1 周は 1.6 秒です。回転はちょうど 1 周で元の角度に戻るので、継ぎ目のないループになります。重なったときにどの点が手前に見えるかは CSS の書く順で決まるので、色は書く順に合わせて割り当てます。

もっと強いイージングに替えると、上に来た瞬間に 4 点がほぼ重なって止まる時間が長くなり、追いかけっこに見えなくなるので避けます。動きを減らす設定を選んでいる場合は、大きく回す代わりに小さな角度でゆっくり行き来させるだけにして、目が疲れる動きを避けています。

点の数や色を増やしても、ずらす間隔をそろえれば同じ形のまま保てます。終わりの見当がつかない待ち時間、たとえば送信中や一覧の読み込み中に向いています。画面には出さない「読み込み中」の一文を添えているので、読み上げにも待っていることが伝わります。

コード

HTML
<div class="orbit-loader" role="status">
  <div class="orbit-loader__ring">
    <div class="orbit-loader__arm"><span class="orbit-loader__dot"></span></div>
    <div class="orbit-loader__arm"><span class="orbit-loader__dot"></span></div>
    <div class="orbit-loader__arm"><span class="orbit-loader__dot"></span></div>
    <div class="orbit-loader__arm"><span class="orbit-loader__dot"></span></div>
  </div>
  <span class="orbit-loader__text">読み込み中</span>
</div>

CSS
.orbit-loader {
  --size: 192px;      /* 輪の直径 */
  --dot: 36px;        /* 点の直径 */
  --ground: #111111;  /* 地の色 */
  --line: rgba(238, 236, 230, 0.14); /* 円周の溝 */
  --c1: #E6D3B3; /* 点の色(先頭 = 最前面)。先頭ほど明るくして、重なっても見分けられるようにする */
  --c2: #A5C0A3;
  --c3: #C78E8A;
  --c4: #6A7D88;
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  background: var(--ground);
}

.orbit-loader__ring {
  position: relative;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  box-shadow: inset 0 0 0 2px var(--line);
}

.orbit-loader__arm {
  position: absolute;
  inset: 0;
}

.orbit-loader__dot {
  position: absolute;
  left: 50%;
  top: 0;
  width: var(--dot);
  height: var(--dot);
  margin: calc(var(--dot) / -2) 0 0 calc(var(--dot) / -2);
  border-radius: 50%;
  background: var(--c4);
}

/* DOM は奥から手前の順に並べてある。手前(最後の子)ほど先頭の色にする */
.orbit-loader__arm:nth-child(2) .orbit-loader__dot { background: var(--c3); }
.orbit-loader__arm:nth-child(3) .orbit-loader__dot { background: var(--c2); }
.orbit-loader__arm:nth-child(4) .orbit-loader__dot { background: var(--c1); }

.orbit-loader__text {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

JavaScript
import { gsap } from "gsap";

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

function createLoader(root) {
  // DOM は奥から手前の順に並べてある。逆順にすると「先頭 = 最前面」の点から並ぶ
  const arms = [...root.querySelectorAll(".orbit-loader__arm")].reverse();

  // sine.inOut で上では遅く下では速く回るので、ずらした腕どうしが上で詰まり下で開いて追いかけっこに見える
  const orbit = gsap.timeline({ repeat: -1 }).fromTo(
    arms,
    { rotation: 0 },
    {
      rotation: reduceMotion ? 28 : 360, // 動きを減らす設定では振れ幅を小さくする
      duration: reduceMotion ? 0.9 : 1.6,
      ease: "sine.inOut",
      stagger: { each: 0.14, repeat: -1, yoyo: reduceMotion },
    }
  );

  return {
    // 読み込みが終わったら呼ぶ。呼ぶまで回り続ける
    done() {
      gsap.to(root, {
        autoAlpha: 0,
        duration: reduceMotion ? 0 : 0.3,
        onComplete: () => {
          orbit.kill();
          root.remove();
        },
      });
    },
  };
}

export const loader = createLoader(document.querySelector(".orbit-loader"));

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

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