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

ローディング中に丸・三角・四角と形を変え続ける

デモ

仕様

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

しくみと調整

1本のパスを MorphSVGPlugin で丸・三角・四角と順に変形し、塗りの色も同じトゥイーンで一緒に移します。角の丸みは塗りと同じ色の太い線で付けているので、形そのものはとがったままで構いません。

回す角度は、形が変わり終えたときに元の向きに見える量にしています。丸→三角→四角の3段階で合計 360° になるので、ループの継ぎ目がつながります。形が変わるあいだは軽く浮き上がり、影も一緒に小さく薄くなります。

変形の中心は、それぞれの形の対称の中心に置く必要があります。ずらすと、回したときに次の形が元の位置からはみ出して重なりません。

形の並びや回す角度、浮き上がる高さを変えると、違うリズムの起動画面になります。デザインツールやクリエイティブ系サービスの起動画面に向いています。

コード

HTML
<div class="morph-loader" role="status">
  <svg class="morph-loader__svg" viewBox="-150 -150 300 300" aria-hidden="true">
    <ellipse class="morph-loader__shadow" cx="0" cy="128" rx="66" ry="9"></ellipse>
    <path class="morph-loader__shape"
      d="M0,-80 C44.18,-80 80,-44.18 80,0 C80,44.18 44.18,80 0,80 C-44.18,80 -80,44.18 -80,0 C-80,-44.18 -44.18,-80 0,-80 Z"></path>
  </svg>
  <span class="morph-loader__text">読み込み中</span>
</div>

CSS
.morph-loader {
  --ground: #f2f1ed;  /* 地の色 */
  --ink: #262626;     /* 影の色 */
  --shape-1: #b27771; /* 丸(くすみローズ) */
  --shape-2: #a28767; /* 三角(グレージュ) */
  --shape-3: #4a5b66; /* 四角(墨のスレート) */
  --size: 300px;
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  background: var(--ground);
}

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

.morph-loader__shadow {
  fill: var(--ink);
  opacity: 0.16;
}

.morph-loader__shape {
  stroke-width: 16;
  stroke-linejoin: round;
}

/* 読み上げにだけ届く文言。画面には出さない */
.morph-loader__text {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

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

gsap.registerPlugin(MorphSVGPlugin);

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
// 形はどれも、回す中心(対称の中心)を原点に置く。そうしないと、回したあと元の向きに重ならない
const TRIANGLE = "M0,-94 L81.41,47 L-81.41,47 Z";
const SQUARE = "M0,-70 L70,-70 L70,70 L-70,70 L-70,-70 Z";
const CIRCLE = "M0,-80 C44.18,-80 80,-44.18 80,0 C80,44.18 44.18,80 0,80 C-44.18,80 -80,44.18 -80,0 C-80,-44.18 -44.18,-80 0,-80 Z";
// turn: 次の形へ移るときに回す角度。合計 360° で丸に戻り、ループがつながる
const SHAPES = [{ d: CIRCLE, turn: 120 }, { d: TRIANGLE, turn: 150 }, { d: SQUARE, turn: 90 }];
const STEP = 1; // 1 つの形にかける時間(秒)
const HOLD = 0.35; // 形を保つ時間。残りで次の形へ移る
const LIFT = 26; // 形を変える間に浮く高さ(viewBox 単位)

function createLoader(root) {
  const shape = root.querySelector(".morph-loader__shape");
  const shadow = root.querySelector(".morph-loader__shadow");
  // 色は CSS が正。GSAP は var(--name) のままでは補間できないので、値にして渡す
  const css = getComputedStyle(root);
  const colors = [1, 2, 3].map((n) => css.getPropertyValue(`--shape-${n}`).trim());
  shape.setAttribute("fill", colors[0]);
  shape.setAttribute("stroke", colors[0]);
  let tl;

  if (reduceMotion) {
    // 動きを減らす設定では形を変えず、明るさだけをゆっくり点滅させる
    tl = gsap.to(root, { opacity: 0.6, duration: 0.9, ease: "sine.inOut", repeat: -1, yoyo: true });
  } else {
    tl = gsap.timeline({ repeat: -1 });
    SHAPES.forEach((current, i) => {
      const next = (i + 1) % SHAPES.length;
      const dur = STEP - HOLD;
      tl.to(shape, { morphSVG: SHAPES[next].d, fill: colors[next], stroke: colors[next], duration: dur, ease: "power2.inOut" }, `+=${HOLD}`)
        .to(shape, { rotation: `+=${current.turn}`, svgOrigin: "0 0", duration: dur, ease: "back.inOut(1.3)" }, "<")
        .to(shape, {
          y: -LIFT, duration: dur / 2, ease: "sine.inOut", yoyo: true, repeat: 1,
          onUpdate: () => {
            const k = 1 + gsap.getProperty(shape, "y") / (LIFT * 4); // 浮くと影が小さく薄くなる
            shadow.setAttribute("rx", (66 * k).toFixed(2));
            shadow.setAttribute("opacity", (0.16 * k).toFixed(3));
          },
        }, "<");
    });
  }

  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(".morph-loader"));

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

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