デモ
デモを読み込めませんでした。
仕様
- 用途
- セクション
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- ScrollTrigger
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
スクロールに合わせて、大小の丸や角丸の四角が上へ流れていきます。手前のものほど速く、奥のものほどゆっくり動き、そのあいだセクションは画面に止まったままです。図形は奥・中・手前の層に分けて散らしてあり、層ごとの動きで変えているのは流れる距離だけです。どの層も同じスクロールのあいだに動くので、距離の違いがそのまま速さの違いとして見えます。距離は奥から、画面の高さの0.4倍・0.9倍・1.6倍です。
奥行きは速さだけに任せず、大きさと色にも受け持たせました。奥の層ほど図形を大きく、背景色に近い色にし、手前の層ほど小さく、背景色との差が大きい色にしています。大きさ・色・速さが同じ方向を指すので、速さの差だけのときより立体的に見えます。中の層だけをローズカラーにしたのは、単調さを避けるためです。
表示部分を止めているのは position: sticky です。セクションを画面の高さの4倍にして、上端が画面の上端に着いてから下端が画面の下端に着くまでを、イージングなしの scrub でスクロール量に連動させています。流す距離は画面の高さから計算するので、invalidateOnRefresh で大きさが変わるたびに測り直します。
図形を置く範囲は、画面の中だけでは足りません。それでは層が上へ流れたあと、下のほうが空になります。縦は「画面の高さ ×(1+流れる距離)」まで散らしておきます。位置と大きさは読み込むたびに乱数で決め直します。決まった配置にしたいなら、固定の値に置き換えてください。
図形の層だけで奥行きを見せる、間奏のようなセクションです。説明する内容を持たないぶん、章の区切りやブランドの世界観を見せる場面に合います。図形の数・大きさ・流れる距離は HTML の属性で、色とセクションの長さは CSS 変数で変えられ、セクションを長くするほど図形はゆっくり流れます。
コード
<!-- 図形だけの飾りのセクションなので、読み上げからは外す -->
<div class="parallax" aria-hidden="true">
<div class="parallax__stage">
<!--
層は奥から手前の順に置く。図形はスクリプトが層ごとに散らして置く。
data-count は図形の数、data-size は大きさ(画面の短い辺に対する比)、
data-travel はセクションを通り抜ける間に流れる距離(画面の高さに対する比)。手前ほど大きくすると速く流れる
-->
<div class="parallax__layer is-far" data-count="10" data-size="0.34" data-travel="0.4"></div>
<div class="parallax__layer is-mid" data-count="14" data-size="0.15" data-travel="0.9"></div>
<div class="parallax__layer is-near" data-count="18" data-size="0.06" data-travel="1.6"></div>
</div>
</div>
.parallax {
--bg: #111111;
--far: #262626; /* 奥の層。背景色に近い色で目立たなくする */
--mid: #d29b97; /* 中の層。アクセントカラー(ローズカラー)で単調にしない */
--near: #eeece6; /* 手前の層。背景色との差が大きい色で浮かせる */
--length: 400vh; /* セクションの長さ。長いほど、ゆっくり流れる */
height: var(--length);
background-color: var(--bg);
}
/* セクションを通り抜ける間、画面に止めておくステージ */
.parallax__stage {
position: sticky;
top: 0;
height: 100vh;
height: 100svh;
overflow: hidden;
}
.parallax__layer { position: absolute; inset: 0; }
.parallax__layer.is-far { --color: var(--far); }
.parallax__layer.is-mid { --color: var(--mid); }
.parallax__layer.is-near { --color: var(--near); }
/* 位置と大きさは、スクリプトが図形ごとに --x・--y・--size で入れる。中心をその位置に合わせる */
.parallax__shape {
position: absolute;
left: var(--x);
top: var(--y);
width: var(--size);
aspect-ratio: 1;
translate: -50% -50%;
border-radius: 50%;
background-color: var(--color);
}
.parallax__layer.is-near .parallax__shape { border-radius: 22%; }
/* 動きを減らす設定では流さないので、セクションも画面 1 つ分の高さにする */
@media (prefers-reduced-motion: reduce) {
.parallax { height: 100vh; height: 100svh; }
}
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
document.querySelectorAll(".parallax").forEach((section) => {
const stage = section.querySelector(".parallax__stage");
const layers = [...section.querySelectorAll(".parallax__layer")];
// 図形を層ごとに散らす。大きさは 0.6〜1.4 倍にばらし、縦は流れる距離の分だけ下まで散らす(流れても、下から次が入ってくる)
for (const layer of layers) {
const size = Number(layer.dataset.size);
const travel = Number(layer.dataset.travel);
for (let i = 0; i < Number(layer.dataset.count); i++) {
const shape = document.createElement("span");
shape.className = "parallax__shape";
shape.style.setProperty("--size", `${(size * 100 * gsap.utils.random(0.6, 1.4)).toFixed(2)}vmin`);
shape.style.setProperty("--x", `${gsap.utils.random(0, 100).toFixed(2)}%`);
shape.style.setProperty("--y", `${gsap.utils.random(0, 100 + travel * 100).toFixed(2)}%`);
layer.append(shape);
}
}
// 動きを減らす設定では、散らした図形を止めたまま見せる
if (reduceMotion) return;
// セクションの上端が画面の上端に着いてから、下端が画面の下端に着くまで(ステージが止まっている間)に流す
const timeline = gsap.timeline({
scrollTrigger: {
trigger: section,
start: "top top",
end: "bottom bottom",
scrub: true, // スクロール量にそのまま連動させる(数値の scrub は実時間で追うので使わない)
invalidateOnRefresh: true, // 画面の高さが変わったら、流す距離を測り直す
},
});
// どの層も同じスクロールの間に動くので、流れる距離の違いがそのまま速さの違いになる
for (const layer of layers) {
const travel = Number(layer.dataset.travel);
timeline.fromTo(layer, { y: 0 }, { y: () => -stage.offsetHeight * travel, ease: "none" }, 0);
}
});