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

ローディング

ローディング中に粘りのある玉がくっついて離れる

デモ

仕様

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

しくみと調整

中心の玉から小さな玉が糸を引くように離れていき、また吸い込まれます。玉どうしがつながって見えるのは SVG フィルターの働きで、まず形をぼかし、そのあとアルファ値を強く持ち上げて、輪郭をくっきりさせ直しています。近くにある玉はぼけた部分が重なって濃くなっているので、くっきりさせたときにひとつの形として残り、水滴がくっついたように見えます。

ぼかしを大きくすれば遠くの玉までつながりそうですが、それでは輪郭がにじみすぎて、玉が丸く見えなくなります。ぼかしは小さめにとどめ、アルファ値の強調のほうを強く効かせて、離れた玉まで糸が届くようにしました。どこまで糸を引かせるかはこの2つの度合いで決まり、両方を上げるほど、遠く離れた玉どうしもつながります。

4つの玉は向かい合う2組に分け、片方が離れていくあいだに、もう片方が戻ってくるようにずらしています。離れきるまでが1.2秒。離れて戻るまでの2.4秒で、全体がゆっくり半回転します。玉の並びは点対称なので、半回転した姿は回る前と同じになり、ループの切れ目が見えません。

コード

HTML
<div class="gooey-loader" role="status">
  <svg class="gooey-loader__svg" viewBox="-100 -100 200 200" aria-hidden="true">
    <defs>
      <filter id="gooey-loader-goo" filterUnits="userSpaceOnUse" x="-100" y="-100" width="200" height="200" color-interpolation-filters="sRGB">
        <feGaussianBlur in="SourceGraphic" stdDeviation="3.8" result="blur" />
        <feColorMatrix in="blur" type="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -5" />
      </filter>
    </defs>
    <g class="gooey-loader__spin" filter="url(#gooey-loader-goo)">
      <g transform="rotate(0)"><circle class="gooey-loader__drop" cx="0" cy="0" r="12" /></g>
      <g transform="rotate(90)"><circle class="gooey-loader__drop" cx="0" cy="0" r="12" /></g>
      <g transform="rotate(180)"><circle class="gooey-loader__drop" cx="0" cy="0" r="12" /></g>
      <g transform="rotate(270)"><circle class="gooey-loader__drop" cx="0" cy="0" r="12" /></g>
      <circle class="gooey-loader__core" cx="0" cy="0" r="17" />
    </g>
  </svg>
  <span class="gooey-loader__text">読み込み中</span>
</div>

CSS
.gooey-loader {
  --size: 200px;      /* SVG の一辺 */
  --ground: #F2F1ED;  /* 明るい背景色 */
  --drop: #4A5B66;    /* 玉の色(濃いスレートカラー) */
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  background: var(--ground);
}

.gooey-loader__svg {
  width: var(--size);
  height: var(--size);
  overflow: visible;
}

.gooey-loader__spin {
  fill: var(--drop);
}

.gooey-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) {
  const spin = root.querySelector(".gooey-loader__spin");
  const drops = root.querySelectorAll(".gooey-loader__drop");

  // 向かい合う 2 組を、片方が離れるあいだもう片方が戻る形にずらす(from / to を逆にした yoyo)
  const goo = gsap.timeline({ repeat: -1 });
  if (reduceMotion) {
    // 全体の回転を止め、離れる幅も小さくする
    goo
      .fromTo([drops[0], drops[2]], { x: 0 }, { x: 12, duration: 1.2, ease: "power2.inOut", repeat: 1, yoyo: true }, 0)
      .fromTo([drops[1], drops[3]], { x: 12 }, { x: 0, duration: 1.2, ease: "power2.inOut", repeat: 1, yoyo: true }, 0);
  } else {
    goo
      .fromTo(spin, { rotation: 0, svgOrigin: "0 0" }, { rotation: 180, svgOrigin: "0 0", duration: 2.4, ease: "none" }, 0)
      .fromTo([drops[0], drops[2]], { x: 0 }, { x: 50, duration: 1.2, ease: "power2.inOut", repeat: 1, yoyo: true }, 0)
      .fromTo([drops[1], drops[3]], { x: 50 }, { x: 0, duration: 1.2, ease: "power2.inOut", repeat: 1, yoyo: true }, 0);
  }

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

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

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

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