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

ヒーロー

ヒーローの背景でぼかした色の塊が漂うオーロラ

デモ

仕様

用途
ヒーロー
動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

ぼかした4つの色の円が、それぞれ違う速さで背景を漂い、重なった所でにじみ合います。位置は横をサイン波、縦をコサイン波で動かし、振れ幅と速さを円ごとに少しずつ変えています。どの円も同じようには動かないので、全体が単純な円運動には見えず、雲が流れるような不規則さが生まれます。

ぼかしは、4つの円すべてを同じ blur(9cqh)にそろえました。いちばん大きい円の直径のおよそ0.15倍にあたり、輪郭がにじみすぎず、隣の円とふわりと溶け合う加減です。これより強いとにじみすぎ、弱いと円の縁が残って溶け合いにくくなります。

位置・大きさ・揺れの振れ幅は、コンテナクエリの単位(cqw と cqh)で指定しています。ヒーローの大きさに対する割合で決まるので、画面の大きさが変わっても JavaScript で計算し直す必要はありません。WebGL を使わず、CSS のぼかしと transform だけで描いているぶん軽く、円の数や色を足していけば、もっとにぎやかな背景にもできます。

コード

HTML
<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>

CSS
.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; }

JavaScript
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));
    });
  });
});

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

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