デモ
デモを読み込めませんでした。
仕様
- 用途
- セクション
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- ScrollTrigger
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
ページの地と文字の色を CSS 変数にまとめ、要素はすべてその変数で塗ります。セクションごとに移る先の配色を別の変数で持たせておき、セクションが画面の中央に入ったら、ページの変数をその値へ gsap.to で移します。動かすのは変数 2 つだけなので、要素ごとに色を書き換えなくて済みます。
切り替えは、セクションの上端が画面の中央を越えたときに、0.6 秒かけて行います。色をスクロール量に比例させると、境目で止めたときに 2 つの配色の中間の濁った色のまま残るので、線を越えたら時間で移します。上へ戻したときも同じで、入り直したセクションの配色に戻ります。ナビの現在地も同じ時刻に移し、aria-current で読み上げにも伝えます。
GSAP は var() のままでは色を補間できません。移す先の色は、各セクションの変数を読み出し、値にしてから渡します。補助の文字の色は、文字と地の色を混ぜる color-mix() で作っておくと、動かす変数が増えず、どの配色でも読める濃さを保てます。
固定したナビの下には、地の色から透明へぼかした帯を敷きます。流れてくる見出しがナビの文字に重ならず、帯も地と同じ変数で塗るので、配色と一緒に色が変わります。ボタンのように文字の色で塗った部品は、配色が替わると明暗も入れ替わります。
配色は、セクションに付けたクラスの 2 色を変えるだけで差し替えられます。切り替えを 1 秒近くまで延ばすと、ゆっくり染まる落ち着いた印象になります。動きを減らす設定では、ほぼ一瞬で切り替えます。事務所の紹介や製品のページなど、章ごとに雰囲気を変えたい 1 ページのサイトに向いています。
コード
<div class="shift">
<!-- 画面の上に固定するナビ。今いるセクションのリンクに aria-current が付く -->
<header class="shift__bar">
<a class="shift__logo" href="/">Hi! I'm HIGASHIYUKI®</a>
<nav aria-label="ページ内">
<ul class="shift__nav">
<li><a href="#studio" aria-current="true">事務所</a></li>
<li><a href="#services">サービス</a></li>
<li><a href="#work">実績</a></li>
<li><a href="#contact">お問い合わせ</a></li>
</ul>
</nav>
</header>
<!-- 配色は、セクションに付けたクラス(shift-paper など)で決める。見出しの行は <br> で文の切れ目に分ける -->
<section class="shift__section shift-paper" id="studio" aria-labelledby="studio-title">
<p class="shift__eyebrow">Studio</p>
<h2 class="shift__title" id="studio-title">ブランドの魅力を、<br>引き出す。</h2>
<p class="shift__lead">ここに説明文が入ります。</p>
</section>
<section class="shift__section shift-slate" id="services" aria-labelledby="services-title">
<p class="shift__eyebrow">Services</p>
<h2 class="shift__title" id="services-title">ブランドも、Webも、<br>動きも。</h2>
<p class="shift__lead">ここに説明文が入ります。</p>
</section>
<section class="shift__section shift-greige" id="work" aria-labelledby="work-title">
<p class="shift__eyebrow">Works</p>
<h2 class="shift__title" id="work-title">ていねいに作り、<br>長く使う。</h2>
<p class="shift__lead">ここに説明文が入ります。</p>
</section>
<section class="shift__section shift-night" id="contact" aria-labelledby="contact-title">
<p class="shift__eyebrow">Contact</p>
<h2 class="shift__title" id="contact-title">まずは、<br>お話ししましょう。</h2>
<p class="shift__lead">ここに説明文が入ります。</p>
<a class="shift__button" href="/contact/">制作を相談する</a>
</section>
</div>
/* 今の配色。スクリプトが、入ったセクションの --theme-bg / --theme-ink の値へ移す。
補助の文字の色は、地と文字の色から作る(動かす色が 2 つで済む) */
.shift {
--bg: #f2f1ed;
--ink: #262626;
--muted: color-mix(in srgb, var(--ink) 70%, var(--bg));
background-color: var(--bg);
color: var(--ink);
}
/* セクションごとの配色。Paper と Night のあいだは、中身のパレットの墨のスレートとグレージュ */
.shift-paper { --theme-bg: #f2f1ed; --theme-ink: #262626; }
.shift-slate { --theme-bg: #4a5b66; --theme-ink: #ffffff; }
.shift-greige { --theme-bg: #d2b999; --theme-ink: #262626; }
.shift-night { --theme-bg: #111111; --theme-ink: #eeece6; }
/* 下の端を透明へぼかした地の色の帯を敷き、流れてくる中身がナビに重ならないようにする */
.shift__bar {
position: sticky;
top: 0;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 16px 32px;
padding: 32px 6vw 48px;
background: linear-gradient(var(--bg) 70%, transparent);
font-size: 14px;
font-weight: 600;
letter-spacing: 0.04em;
}
.shift a {
color: inherit;
text-decoration: none;
transition: opacity 0.6s;
}
.shift a:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
.shift__logo { font-size: 18px; font-weight: 700; letter-spacing: 0.12em; }
.shift__nav {
display: flex;
flex-wrap: wrap;
gap: 8px clamp(20px, 2.5vw, 32px);
white-space: nowrap; /* 和文の項目名を字の途中で折り返さない */
margin: 0;
padding: 0;
list-style: none;
}
/* 今いるセクションのリンクだけを濃くする */
.shift__nav a:not([aria-current]) { opacity: 0.45; }
.shift__section {
display: grid;
align-content: center;
min-height: 100vh;
box-sizing: border-box;
padding: 64px 6vw 96px;
}
.shift__eyebrow {
margin: 0;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
}
.shift__title {
margin: 24px 0 0;
font-size: clamp(32px, 6vw, 72px);
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.02em;
word-break: auto-phrase;
}
.shift__lead {
max-width: 30em;
margin: 24px 0 0;
font-size: 18px;
line-height: 1.8;
letter-spacing: 0.04em;
}
/* 文字の色で塗り、地の色の文字を載せる。セクションの配色に合わせて明暗が入れ替わる */
.shift .shift__button {
justify-self: start;
margin-top: 48px;
padding: 14px 32px;
border-radius: 999px;
background-color: var(--ink);
color: var(--bg);
font-size: 15px;
font-weight: 600;
}
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const NAMES = ["bg", "ink"]; // 移す色(ページの --bg / --ink と、セクションの --theme-bg / --theme-ink)
// 動きを減らす設定では、ほぼ一瞬で切り替える
const SWAP = window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0.05 : 0.6;
document.querySelectorAll(".shift").forEach((page) => {
const sections = gsap.utils.toArray(".shift__section", page);
const links = gsap.utils.toArray(".shift__nav a", page);
// GSAP は var(--name) のままでは補間できないので、セクションごとの配色を値にして持っておく
const themes = sections.map((section) => {
const styles = getComputedStyle(section);
return Object.fromEntries(NAMES.map((name) => [`--${name}`, styles.getPropertyValue(`--theme-${name}`).trim()]));
});
const show = (i) => {
// 切り替えの途中で次のセクションに入っても、今の色からそのまま移る
gsap.to(page, { ...themes[i], duration: SWAP, ease: "power1.inOut", overwrite: "auto" });
for (const link of links) {
if (link.hash === `#${sections[i].id}`) link.setAttribute("aria-current", "true");
else link.removeAttribute("aria-current");
}
};
sections.forEach((section, i) =>
ScrollTrigger.create({
trigger: section,
start: "top center", // セクションの上端が画面の中央を越えたら
end: "bottom center",
// スクロール量には比例させず、入ったセクションの配色へ時間で移す。上へ戻したときも同じ
onToggle: (self) => self.isActive && show(i),
})
);
});