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

ローディング

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

デモ

仕様

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

しくみと調整

波紋のような輪が、線の細さを変えないまま次々と広がっては消えていきます。真ん中の点は、輪が生まれる瞬間にいちばん大きくなるように脈打っています。

輪は3本です。輪ひとつが広がって消えるまでの2.4秒を本数で割り、0.8秒ずつ開始をずらしました。前の輪が消えきる前に次が生まれるので、波紋は途切れません。中心の点も、同じ0.8秒の間隔で拍動させています。

scale で拡大すれば手軽ですが、それだと線の太さまで一緒に太ってしまいます。そこで circle の半径そのものを、GSAP の attr でトゥイーンしています。どこまで広がっても、線の太さは最初のままです。

広がりには power2.out、薄れ方には power1.in と、別々のイージングを使いました。大きさは生まれた直後がいちばん速く、だんだん落ち着きます。一方で濃さははじめほとんど変わらず、終わりに近づくほど速く抜けていくので、輪ははっきり見えたまま勢いよく広がり、広がりきる手前で消えます。

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