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

ローディング

ローディング中に円弧が伸び縮みしながら回る

デモ

仕様

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

しくみと調整

弧はいちばん縮んだときも全周の8%ぶん残り、その短い切れ端のまま回りながら、また長く伸びていきます。長さの操作には DrawSVGPlugin を使いました。弧の始まりと終わりを%で指定できるので、円周の長さを計算せずに済みます。伸びる前半と縮む後半には、それぞれ power1.inOut をかけています。

回転の角度には、少し上乗せがあります。弧は一度伸び縮みを終えると、始めの位置から全周の8%ずれた所にいます。回転を360°ちょうどにしておくと、このずれを抱えたまま次の周に入るので、変わり目で弧が飛んで見えます。8%に当たる28.8°を足して、周ごとの継ぎ目を消しました。

回転の軸は、svgOrigin で円の中心に固定しています。軸と中心がずれていると、弧が輪の線の上から外れて回ってしまいます。輪の大きさや色、回る速さを変えれば、送信中のボタンの中にも、読み込み中の全画面表示にも、そのまま使い回せます。

コード

HTML
<div class="spin-loader" role="status">
  <svg class="spin-loader__svg" viewBox="0 0 100 100" aria-hidden="true">
    <circle class="spin-loader__track" cx="50" cy="50" r="40"></circle>
    <circle class="spin-loader__arc" cx="50" cy="50" r="40"></circle>
  </svg>
  <span class="spin-loader__text">読み込み中</span>
</div>

CSS
.spin-loader {
  --ground: #f2f1ed;               /* 背景色 */
  --line: rgba(38, 38, 38, 0.12);  /* トラック(下地の輪)の色 */
  --arc: #b27771;                  /* 弧の色(アクセントカラー。ローズカラー) */
  --size: 144px;                   /* 輪の大きさ */
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  background: var(--ground);
}

.spin-loader__svg {
  display: block;
  width: var(--size);
  height: var(--size);
  overflow: visible;
}

.spin-loader__track,
.spin-loader__arc {
  fill: none;
  stroke-width: 9;
  stroke-linecap: round;
}

.spin-loader__track { stroke: var(--line); }
.spin-loader__arc { stroke: var(--arc); }

/* スクリーンリーダーにだけ届く文言。画面には表示しない */
.spin-loader__text {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

JavaScript
import { gsap } from "gsap";
import { DrawSVGPlugin } from "gsap/DrawSVGPlugin";

gsap.registerPlugin(DrawSVGPlugin);

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const DURATION = 1.4; // 1 周の時間(秒)
const SEGMENT = 8; // 縮みきったときの弧の長さ(%)

function createLoader(root) {
  const arc = root.querySelector(".spin-loader__arc");
  let tl;

  if (reduceMotion) {
    // 動きを減らす設定では回さず、明るさだけをゆっくり点滅させる
    gsap.set(arc, { drawSVG: "0% 30%" });
    tl = gsap.to(root, { opacity: 0.5, duration: 0.9, ease: "sine.inOut", repeat: -1, yoyo: true });
  } else {
    // 弧は 8% ぶん先へ進んで縮むので、回転に 8% 分(28.8°)を足すと 1 周で継ぎ目が出ない
    tl = gsap
      .timeline({ repeat: -1 })
      .fromTo(arc, { rotation: -90, svgOrigin: "50 50" }, { rotation: -90 + 360 + SEGMENT * 3.6, svgOrigin: "50 50", duration: DURATION, ease: "none" }, 0)
      .fromTo(arc, { drawSVG: `0% ${SEGMENT}%` }, { drawSVG: "0% 78%", duration: DURATION / 2, ease: "power1.inOut" }, 0)
      .to(arc, { drawSVG: `${100 - SEGMENT}% 100%`, duration: DURATION / 2, ease: "power1.inOut" }, DURATION / 2);
  }

  return {
    // 読み込みが終わったら呼ぶ
    done() {
      gsap.to(root, {
        autoAlpha: 0,
        duration: reduceMotion ? 0 : 0.3,
        onComplete: () => {
          tl.kill();
          root.remove();
        },
      });
    },
  };
}

// 例: 送信中のボタンや、読み込み中の全画面表示に。終わったら loader.done()
export const loader = createLoader(document.querySelector(".spin-loader"));

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

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