デモ
デモを読み込めませんでした。
仕様
- 用途
- ヒーロー
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- MorphSVGPlugin
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
3 つのブロブの形を MorphSVGPlugin で順に変形し、色も一緒に移しながらゆっくり回ります。各区間の前半は形を保ち、後半だけで次の形へ移るので、変形が唐突に始まらず落ち着いて見えます。
回転は一定の速さのまま回し続け、変形だけに sine.inOut の ease をかけています。速さの異なる 2 つの動きを重ねることで、ただ回るだけよりも複雑な印象になります。
3 つ目の形を最初の形へつなぐと、途中で止めてもループの継ぎ目が目立ちません。形の数を増やす場合も、同じ考え方で最後を最初につなげば継ぎ目なく回せます。
つまみは形の数・1 つの形にとどまる時間・色です。地は生成りのままにしておくと、ブロブの色が引き立ちます。ヒーローの装飾やローディングの背景など、視線を集めたいが主役ではない場所に向いています。
コード
<section class="blob-hero">
<!-- 絵は飾りなので、読み上げからは外す -->
<svg class="blob-hero__art" viewBox="0 0 600 600" aria-hidden="true">
<path class="blob-hero__shape"></path>
</svg>
<div class="blob-hero__copy">
<p class="blob-hero__eyebrow">Studio</p>
<h1 class="blob-hero__title">かたちは、<wbr>変わりつづける。</h1>
<p class="blob-hero__lead">ここに説明文が入ります。</p>
</div>
</section>
.blob-hero {
--bg: #f2f1ed;
--ink: #262626;
--blob-1: #b27771; /* ブロブの色(形の順)。明るさを離すと、形が移るのが見分けやすい */
--blob-2: #98b1c4;
--blob-3: #4a5b66;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
align-items: center;
gap: clamp(24px, 6vw, 64px);
padding: 96px clamp(24px, 7vw, 96px);
background-color: var(--bg);
color: var(--ink);
}
.blob-hero__art {
display: block;
width: 100%;
max-width: 420px;
}
.blob-hero__eyebrow {
margin: 0;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.blob-hero__title {
margin: 24px 0 0;
font-size: clamp(32px, 5vw, 56px);
line-height: 1.1;
letter-spacing: -0.02em;
word-break: keep-all; /* 和文を語の途中で折らず、<wbr> の所で折り返す */
}
.blob-hero__lead {
margin: 24px 0 0;
max-width: 30em;
font-size: 18px;
line-height: 1.8;
}
@media (max-width: 640px) {
.blob-hero { grid-template-columns: minmax(0, 1fr); }
}
import { gsap } from "gsap";
import { MorphSVGPlugin } from "gsap/MorphSVGPlugin";
gsap.registerPlugin(MorphSVGPlugin);
const STEP = 1.2; // 1 つの形にとどまってから次へ移るまでの時間(秒)
// 3 つのブロブ形。前半は形を保ち、後半で次の形へ移る。色は CSS の --blob-1〜3
const SHAPES = [
"M300,90 C420,90 510,170 510,300 C510,430 420,510 300,510 C180,510 90,430 90,300 C90,170 180,90 300,90 Z",
"M300,70 C450,110 540,200 500,330 C460,470 370,540 260,500 C130,460 60,380 90,250 C120,130 190,40 300,70 Z",
"M320,110 C430,60 540,160 520,280 C500,420 430,520 300,520 C160,520 70,420 110,300 C140,200 200,150 320,110 Z",
];
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
document.querySelectorAll(".blob-hero__shape").forEach((blob) => {
const css = getComputedStyle(blob);
const fills = SHAPES.map((_, i) => css.getPropertyValue(`--blob-${i + 1}`).trim());
blob.setAttribute("d", SHAPES[0]);
blob.setAttribute("fill", fills[0]);
if (reduceMotion) return; // 動きを減らす設定では、最初の形のまま止めておく
const tl = gsap.timeline({ repeat: -1 });
tl.fromTo(blob, { rotation: 0, svgOrigin: "300 300" }, { rotation: 360, svgOrigin: "300 300", duration: SHAPES.length * STEP, ease: "none" }, 0);
SHAPES.forEach((_, i) => {
const next = (i + 1) % SHAPES.length;
tl.to(blob, { morphSVG: SHAPES[next], fill: fills[next], duration: STEP * 0.6, ease: "sine.inOut" }, i * STEP + STEP * 0.4);
});
});