デモ
デモを読み込めませんでした。
仕様
- 用途
- ヒーロー
- 動作
- DOM
- 種類
- レシピ
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
ぼかした円を 4 つ重ね、それぞれ別の速さでゆっくり漂わせています。位置は円ごとにサイン波とコサイン波を組み合わせて決めているので、単純な円運動にならず、雲のような不規則さが出ます。
揺れの振れ幅や速さは、円ごとに少しずつ変えています。blur の強さは円の直径のおよそ 0.15 倍が、輪郭がにじみすぎずふわりと溶け合う目安です。
WebGL を使わない分、画面の大きさが変わっても位置や大きさを計算し直す手間がかからないよう、コンテナクエリの単位で指定しています。動きを減らす設定のときは、円を揺らさず中央の位置で止めます。
円の数や色を増やせば、にぎやかな印象にもできます。軽くてどんな背景にも置きやすいので、シンプルな背景の飾りに向いています。
コード
<section class="aurora-hero">
<!-- 背景は飾りなので、読み上げからは外す -->
<div class="aurora-hero__field" aria-hidden="true"></div>
<div class="aurora-hero__inner">
<p class="aurora-hero__eyebrow">Morning</p>
<h1 class="aurora-hero__title">深く息をして、<br>朝をはじめる。</h1>
<p class="aurora-hero__lead">ここに説明文が入ります。</p>
<a class="aurora-hero__button" href="/start/">無料で試す</a>
</div>
</section>
.aurora-hero {
--bg: #f2f1ed;
--ink: #262626;
/* 色だまりの色。script.js はこの名前だけを参照し、値はここでだけ決める */
--blob1: #e48b85; /* ローズ(淡) */
--blob2: #4f85b0; /* スレート(濃い段で奥行きを出す) */
--blob3: #e4bb87; /* グレージュ(淡) */
--blob4: #aa95da; /* ラベンダー(淡) */
--gutter: clamp(24px, 7vw, 96px);
position: relative;
display: grid;
align-items: center;
min-height: min(90vh, 720px);
padding: 0 var(--gutter);
overflow: hidden;
color: var(--ink);
background-color: var(--bg);
}
.aurora-hero__field {
position: absolute;
inset: 0;
container-type: size; /* 子の cqw / cqh を、この箱の大きさから決める */
}
.aurora-hero__blob {
position: absolute;
left: var(--x);
top: var(--y);
width: var(--size);
height: var(--size);
border-radius: 50%;
opacity: 0.85;
filter: blur(9cqh);
/* 位置は左上を基準に中央へ寄せ、揺れは -1..1 の値(--sx / --sy)に振れ幅(--ax / --ay)を掛けて足す */
transform: translate(-50%, -50%) translate(calc(var(--sx, 0) * var(--ax)), calc(var(--sy, 0) * var(--ay))) scale(var(--sc, 1));
}
.aurora-hero__inner {
position: relative; /* 背景の上に載せる */
max-width: 34em;
}
.aurora-hero__eyebrow {
margin: 0;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.aurora-hero__title {
margin: 24px 0 0;
font-size: clamp(40px, 8vw, 72px);
line-height: 1.05;
letter-spacing: -0.02em;
}
.aurora-hero__lead {
margin: 24px 0 0;
font-size: 18px;
line-height: 1.8;
}
.aurora-hero__button {
display: inline-grid;
place-items: center;
height: 48px;
margin-top: 32px;
padding: 0 32px;
border-radius: 999px;
background-color: var(--ink);
color: var(--bg);
font-size: 15px;
font-weight: 600;
letter-spacing: 0.04em;
text-decoration: none;
}
.aurora-hero__button:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
import { gsap } from "gsap";
const DURATION = 10; // ひと回りして同じ位置に戻るまで(秒)
// x, y: 中心(画面比) / size: 大きさ(高さ比) / ax, ay: 揺れの振れ幅(画面比) / fx, fy: 揺れの周波数
// color: 色は CSS 変数の名前(値そのものは style.css の --blob1 などに書く)
const BLOBS = [
{ color: "--blob1", size: 0.62, x: 0.3, y: 0.36, ax: 0.1, ay: 0.08, fx: 1, fy: 2, phase: 0 },
{ color: "--blob2", size: 0.58, x: 0.68, y: 0.4, ax: 0.12, ay: 0.1, fx: 2, fy: 1, phase: 1.3 },
{ color: "--blob3", size: 0.46, x: 0.52, y: 0.72, ax: 0.14, ay: 0.06, fx: 1, fy: 1, phase: 2.4 },
{ color: "--blob4", size: 0.4, x: 0.2, y: 0.76, ax: 0.08, ay: 0.12, fx: 2, fy: 3, phase: 4.1 },
];
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
document.querySelectorAll(".aurora-hero__field").forEach((field) => {
const nodes = BLOBS.map((blob) => {
const node = document.createElement("div");
node.className = "aurora-hero__blob";
// 位置・大きさ・振れ幅は cqw / cqh(この箱の大きさに対する %)にしておくと、画面の大きさが変わっても計算し直さずに済む
node.style.cssText = `--x:${blob.x * 100}cqw;--y:${blob.y * 100}cqh;--size:${blob.size * 100}cqh;--ax:${blob.ax * 100}cqw;--ay:${blob.ay * 100}cqh;background-color:var(${blob.color});`;
field.append(node);
return node;
});
if (reduceMotion) return; // 揺れの振れ幅(--sx / --sy)を書かなければ、中央で止まったまま
gsap.ticker.add((time) => {
const angle = ((time % DURATION) / DURATION) * Math.PI * 2;
BLOBS.forEach((blob, i) => {
nodes[i].style.setProperty("--sx", Math.sin(angle * blob.fx + blob.phase).toFixed(4));
nodes[i].style.setProperty("--sy", Math.cos(angle * blob.fy + blob.phase).toFixed(4));
nodes[i].style.setProperty("--sc", (1 + 0.08 * Math.sin(angle * 2 + blob.phase)).toFixed(4));
});
});
});