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

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

デモ

仕様

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

しくみと調整

中心の玉から 4 つの小さな玉が糸を引くように離れ、また吸い込まれます。粘りの正体は SVG フィルターで、ぼかした形をアルファ値で強調し直すと、近づいた玉どうしのぼかしがひとつの形として残り、水滴がつながったように見えます。

向かい合う 2 組の玉を、片方が離れるあいだもう片方が戻る形にずらしていて、離れきるまで 1.2 秒、全体はそのあいだにゆっくり半回転します。形が点対称なので、1 ループで半回転させると元の並びに戻り、継ぎ目なくつながります。

ぼかしを強くしすぎると輪郭がにじみすぎて玉の形が崩れるので、ぼかしは小さめにして、代わりにアルファ値の強調を効かせて遠くの玉までつなげています。動きを減らす設定では全体の回転を止め、離れる幅も小さくして、大きな動きを見せないようにしています。

ぼかしと強調の度合いを上げれば、もっと離れた玉どうしも糸を引くようになります。終わりの見当がつかない待ち時間の、やわらかい印象を出したい場面に向いています。

コード

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