JavaScriptのIntersectionObserverAPIでスティッキーナビを実装する方法

2021年3月1日JavaScript

JavaScriptのIntersectionObserverでスティッキーナビゲーションを実装する方法を紹介します。
window.addEventListenerのscrollよりもパフォーマンスが改善されるメリットがあります。
window.addEventListenerのscrollでのスティッキーナビゲーションでの実装方法と比較してみましょう。

window.addEventListenerのscrollでのスティッキーナビゲーション



//スクロール先の定数
const section1 = document.querySelector('#section--1');

//scrollイベントで設定する(パフォーマンス悪い)
const initialCoords = section1.getBoundingClientRect();
// console.log(initialCoords);

window.addEventListener('scroll', function () {
  // console.log(window.scrollY);

  if (window.scrollY > initialCoords.top) nav.classList.add('sticky');
  else nav.classList.remove('sticky');
});

スクロールイベントはdocumentではなく、windowにイベントハンドラーを設定します。
そして、window.scrollYとするとスクロールしたときのトップからのY座標を確認できます。
そして、特定の場所のY座標はgetBoundingClientRect()のtopを確認するとわかります。
そのため、スクロールしたY座標が特定の場所のY座標を超えたら、クラスを追加してあげればOKです。
これでも動きますが、scrollイベントはパフォーマンスがとても悪いです。その理由は、動くたびにスクロールの座標が取得されてしまうためです。モバイル環境では特におすすめできません。

intersection observerとは

intersectionとは、物体の交差という意味です。
observerとは、監視するものという意味です。
intersectionObserverは、物体の交差を監視するものという意味です。

intersection observer APIの基本的な使い方

// 交差を監視したいもの
const section1 = document.querySelector('.section-1');
// 交差したときに呼び出すコールバック関数
const cb = function() {
  console.log('交差しました');
};
// IntersectionObserverの初期化
const io = new IntersectionObserver(cb);
// 監視を開始する
io.observe(section1);
  1. 交差を監視したいもの設定する
  2. 交差したときに呼び出すコールバック関数を作成する
  3. IntersectionObserverの初期化する
  4. 監視を開始する

という流れが基本となります。
new演算子でIntersectionObserverにコールバック関数を渡して初期化します。
それを変数に格納し、監視したいDOMを登録することで監視を開始できます。
監視を開始するとき、observe()メソッドを使います。
このようにすることで、section-1というクラスをもった要素が画面内に入るときと出るときに、毎回IntersectionObserverに設定したコールバック関数が呼ばれます。

intersection observer APIの具体的な使い方


//スクロール先の定数
const section1 = document.querySelector('#section--1');

//コールバック関数
const obsCallback = function (entries, observer) {
  entries.forEach(entry => {
    console.log(entry);
  });
};
const obsOptions = {
  root: null, //rootプロパティ, nullでブラウザーのビューポートを使用することができます。
  threshold: 0.1, //しきい値。0.1は10%の意味です。
};

//IntersectionObserver
const observer = new IntersectionObserver(obsCallback, obsOptions);
observer.observe(section1); //このsection1がターゲットとなります。

IntersectionObserverの初期化

intersection observer APIは、new IntersectionObserver(コールバック関数, オプション)とすることで初期化できます。
上の例では、それをobserverという変数に格納しています。

IntersectionObserverのコールバック関数

コールバック関数では、entriesとobserverという2つの引数をとります。
observerはIntersectionObserverの初期化したものです。
entriesについて使い方を詳しくみてみましょう。

コールバック関数の第一引数entries

entriesについて、IntersectionObserverには、次のように複数の要素を登録することができます。

observer.observe(section1); //このsection1がターゲットとなります。
observer.observe(section2); //このsection2がターゲットとなります。
observer.observe(section3); //このsection3がターゲットとなります。

複数の要素を登録したときは、それぞれの要素が画面内に出たときと入ったときにコールバック関数が呼ばれることになります。それぞれに処理するためにforEach()で処理を記述します。
各々をentryとして、isIntersectingというプロパティを使って、trueの場合は要素がある状態という意味になります。
isIntersectingプロパティがfalseのときは、監視から外れたときを意味します。

//コールバック関数
const obsCallback = function (entries, observer) {
  entries.forEach(entry => {
    if(entry.isIntersecting) {
      console.log(`${entry}が画面にあります`);
      // 1回出たら監視をやめたいとき
      observer.unobserve(entry.target);
    } else {
      console.log(`${entry}が画面から出ました`);
    }
  });
};

1回監視をしたら、それ移行は監視をやめたいという場合、unobserve()メソッドに監視対象(entry)のtarget(section-1)を渡します。

entriesは常に配列

entriesはthresholdの配列となります。
たとえ値が一つでも配列になります。
IntersectionObserverのoptionsは複数のthresholdを保持できるためです。

このように、addEventListenerのscrollとは違い、交差した時点でのみ反応させることができます。

IntersectionObserverのオプション

Intersection observerのオプションを設定することで、コールバック関数を制御できます。

Intersection observerのオプションには、次の3つのを設定します。

  • root
  • threshold
  • rootMargin

root:nullでビューポート

rootはターゲットを観測するためのビューポートの設定です。
交差対象とした親や先祖の要素を設定できます。親や先祖の要素を設定すると、監視対象と設定した親や先祖要素が交差した時点でコールバック関数を呼び出すことができます。

nullもしくは指定をしないことで、ブラウザーのビューポートを使用することができます。実際はnullでの設定になることが多いでしょう。

上の場合、ターゲット要素であるobserver.observe(section1)がroot、つまりビューポートの10%(thresholdの値)に入ったらobsCallbackを実行することができます。
スクロールアップでもスクロールダウンでも実行されます。

thresholdはしきい値

thresholdはしきい値・閾値の意味です。限界とか境界といった意味に近いです。
0〜1を指定します。スクロールアップでもスクロールダウンでも、監視対象エリアに監視対象が最初に入る部分を0、監視対象が最後には入る部分が1です。

オブザーバーのコールバック(この場合はobsCallback)を実行するターゲットがどのくらいの割合で見えているかを%で示します。
0.1は10%の意味です。監視対象のものは、ブラウザに10%入ったらコールバック関数が呼び出されます。

console.log(entry)でIntersectionObserverEntryを取得できます。その中のintersectionRatioを確認すると0.1前後であることが確認できます。
thresholdは配列で指定することもできます。[0, 0.5, 1]といった具合です。監視対象がとても大きい場合は配列にすることで、それぞれの地点でコールバック関数を呼び出すことができます。

rootMargin

rootMarginはCSSのmarginのようなものです。pxや%が使えます。

表示を早めたいときや、遅くしたいときに利用します。

プラスの値を設定すると、Y軸で上方向にmarginが設定されます。
設定するときの注意点としては、値が一つの場合上下左右が設定されてしまい、左右のマージンが原因で要素が観測されない場合があるので、
rootMargin: “300px 0px"とするなど、意図した挙動となるように設定しましょう。

intersection observer APIを使ったスティッキーナビゲーション



//targetを定義
  const header = document.querySelector('.header');

//rootMarginを決めるために高さを取得
  const navHeight = nav.getBoundingClientRect().height;

//コールバック関数
  const stickyNav = function(entries) {

  //entriesは常に配列
    const [entry] = entries;//entries[0]と同じ意味
    console.log(entry);
  //Guard clause(Guard Clause ガード節,ガード条件)
  // targetであるheaderがroot、つまりビューポートから見えなくなったらstickyをつけます。
    if(!entry.isIntersecting) nav.classList.add('sticky');
    else nav.classList.remove('sticky');
  }

//IntersectionObserverの設定、今回はoptionsをそのまま記載
  const headerObserver = new IntersectionObserver(stickyNav, {
    root: null,
    threshold: 0, //headerが完全に見えなくなったらスティッキーにしたい
    rootMargin: `-${navHeight}px`, 
});

//呼び出し
  headerObserver.observe(header);

IntersectionObserverの設定は、今回はoptionsをそのまま記載しています。
targetであるheaderがroot、つまりビューポートから見えなくなったらstickyをつけて、headerがビューポートで見えるようになったらstickyを外すというコールバック関数です。
entriesの中身は一つですが、配列なので上のような処理になります。
rootMarginはrootつまりビューポート内でターゲットであるheaderがみえなくなると判定するY軸の基準点を調整できます。マイナスなら上に、プラスの値なら下にずらせます。

上の例のように、thresholdを0にして、rootMarginで調整するといいでしょう。
rootMarginの高さは、ナビゲーションのgetBoundingClientRect().heightで取得できます。
画面幅で変わるため、このように設定するのが望ましいです。

IntersectionObserverのthis

IntersectionObserverはwindow.IntersectionObserverなのでthisはwindowを指します。
うまく動かない場合、

new IntersectionObserver(callback.bind(this), this.options);

上のようにbind(this)などでthisの参照先をコントロールしましょう。