グローバルナビをエリアに応じて色を変える方法

この記事の前提条件

対象: JavaScript(全モダンブラウザ対応)
難易度: ★★☆
所要時間: 約15分

1ページ完結のLP(ランディングページ)やコーポレートサイトのトップページで、白背景のセクションでは黒文字のロゴ、濃い色の背景セクションに差し掛かったら白文字のロゴに切り替わる…という演出、見かけたことがあると思います。スクロール位置に応じてグローバルナビの色を自動で変える実装を紹介します。

各セクションに「背景の色味」を示す目印を付ける

まず、色を切り替える判断材料として、各セクションのHTMLにdata-theme属性で背景が明るいか暗いかを示しておきます。

<header class="global-nav">...</header>

<section data-theme="light">明るい背景のセクション</section>
<section data-theme="dark">暗い背景のセクション</section>
<section data-theme="light">明るい背景のセクション</section>

IntersectionObserverでナビ直下のセクションを監視する

スクロールイベントを都度計算する方法は処理が重くなりがちなので、IntersectionObserverを使い、「ナビゲーションのすぐ下の位置に、どのセクションが来ているか」を効率よく監視します。

const nav = document.querySelector('.global-nav');
const sections = document.querySelectorAll('[data-theme]');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      nav.dataset.theme = entry.target.dataset.theme;
    }
  });
}, {
  rootMargin: '-1px 0px -99% 0px' // ナビのすぐ下、1px分の細い判定ラインを作る
});

sections.forEach(section => observer.observe(section));

rootMarginの値がやや特殊に見えますが、これは「画面の一番上から1px分の細い帯だけを判定対象にする」という設定です。ナビゲーションのすぐ下を通過したセクションだけを検知できるため、画面の途中に別セクションが見えている間は反応せず、意図通りの切り替えタイミングになります。

.global-nav {
  transition: color 0.3s, background-color 0.3s;
}

.global-nav[data-theme="dark"] {
  color: #fff;
}

.global-nav[data-theme="light"] {
  color: #333;
}
⚠️ scrollイベントでの監視は避ける

古い実装ではwindow.addEventListener('scroll', ...)の中で各セクションの座標を毎回計算する方法もよく見かけますが、スクロール中は1秒間に何十回も発火するイベントのため、処理内容によっては画面のカクつきの原因になります。IntersectionObserverはブラウザ側が効率よく監視してくれる仕組みのため、同じ見た目を実現するなら、今はこちらを使うのが標準的です。

まとめ

  • 各セクションにdata-theme属性で背景の明暗を持たせておく
  • IntersectionObserverでナビ直下を通過したセクションを検知し、ナビ側のdata-themeを書き換える
  • 色の変化にはtransitionを付けて、切り替わりを滑らかにする
  • スクロールイベントでの座標計算は処理が重くなりがちなので避ける