本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100
セクション

スクロールでセクションに合わせて背景色と文字色が切り替わる

デモ

操作:スクロール。 「さわってみる」を押すと自動再生が止まり、マウスで動かせます。 触っている間は、ホイールでデモの中がスクロールします。

仕様

動作
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 ページのサイトに向いています。

コード

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

CSS
/* 今の配色。スクリプトが、入ったセクションの --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;
}

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

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

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