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

ローディング中に波紋が広がって消える

デモ

仕様

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

しくみと調整

SVG の circle の半径を attr でトゥイーンして輪を広げ、線の太さを保ったまま波紋のように見せます。3本の輪を等間隔でずらして繰り返すので、輪が途切れずに次々と生まれ続けます。中心の点は輪が生まれる瞬間にいちばん大きくなるよう、同じ間隔で拍動させています。

1本の輪が広がって消えるまでは 2.4 秒、3本なので周期を3で割った間隔でずらすと、途切れずに出続けます。広がりには power2.out、消え方には power1.in と別のイージングを充てているので、生まれた直後は速く、消える間際はゆっくり薄れます。

半径は scale ではなく attr で変えています。拡大縮小だと線の太さまで一緒に伸び縮みしてしまうためです。輪と中心の点の色は CSS の変数ひとつで決めているので、地の色を変えたときはその変数を合わせるだけで済みます。

本数や広がる大きさ、繰り返す間隔を変えると、密度の違う波紋になります。読み込み中だけでなく、「接続中」「録音中」「ライブ」のような、終わりの見えない状態を示す表示にも使えます。

コード

HTML
<div class="pulse-loader" role="status">
  <svg class="pulse-loader__svg" viewBox="-100 -100 200 200" aria-hidden="true">
    <circle class="pulse-loader__ring" cx="0" cy="0" r="14"></circle>
    <circle class="pulse-loader__ring" cx="0" cy="0" r="14"></circle>
    <circle class="pulse-loader__ring" cx="0" cy="0" r="14"></circle>
    <circle class="pulse-loader__core" cx="0" cy="0" r="14"></circle>
  </svg>
  <span class="pulse-loader__text">読み込み中</span>
</div>

CSS
.pulse-loader {
  --ink: #262626;    /* 輪と中心の点の色 */
  --ground: #f2f1ed; /* 地の色 */
  --size: 232px;     /* SVG の一辺 */
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  background: var(--ground);
}

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

.pulse-loader__ring {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.5;
}

.pulse-loader__core {
  fill: var(--ink);
}

/* 読み上げにだけ届く文言。画面には出さない */
.pulse-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;
const COUNT = 3;
const PERIOD = 2.4; // 1 本の輪が広がって消えるまで(秒)
const EACH = PERIOD / COUNT; // 輪の間隔。周期を本数で割ると、途切れずに出続ける
const R0 = 14; // 輪の出はじめの半径(viewBox 単位。中心の点と同じ)
const R1 = reduceMotion ? 30 : 97; // 消えるときの半径。動きを減らす設定では広がりを小さくする

function createLoader(root) {
  const rings = root.querySelectorAll(".pulse-loader__ring");
  const core = root.querySelector(".pulse-loader__core");

  const pulse = gsap
    .timeline()
    .fromTo(rings, { attr: { r: R0 } }, { attr: { r: R1 }, duration: PERIOD, ease: "power2.out", stagger: { each: EACH, repeat: -1 } }, 0)
    .fromTo(rings, { opacity: 0.9 }, { opacity: 0, duration: PERIOD, ease: "power1.in", stagger: { each: EACH, repeat: -1 } }, 0)
    // 輪が出る瞬間(EACH の倍数)に scale 1、その間に 0.78 まで縮む
    .fromTo(core, { scale: 1 }, { scale: 0.78, duration: EACH / 2, ease: "sine.inOut", repeat: -1, yoyo: true }, 0);

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

// 例: fetch や画像の読み込みを待って loader.done()。「接続中」「録音中」「ライブ」の表示にも使える
export const loader = createLoader(document.querySelector(".pulse-loader"));

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

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