📚

【JavaScript】Web APIインターフェースを使ってみた-其の2:IntersectionObserver

に公開

このシリーズについて

JavaScriptのWeb APIインターフェースについて、どんなケースで使ったのかという具体例やコードも含めて、シンプルにその有用性についてまとめよう!というシリーズです。

※MDNで多数のAPIとそのインターフェースが公開されています。
https://developer.mozilla.org/ja/docs/Web/API

2本目となる記事で取り上げるのは、こちらです。

IntersectionObserver

交差オブザーバーAPIのインターフェスである【IntersectionObserver】を取り上げます。
https://developer.mozilla.org/ja/docs/Web/API/IntersectionObserver

どんなことができるか

監視対象のターゲットが、見ている端末のビューポート(現在表示されている範囲)または指定要素とどれくらい交差したかを監視してくれます。
交差時や交差しなくなった時を検知して、コールバック関数を実行させることができます。

また、IntersectionObserverコンストラクターにオプションのオブジェクトを渡してあげると、ルート要素の指定や交差率など、交差時の判定条件を変更することも可能です。(このオプションについては後述)

使用した具体例

「指定したセクションが表示されたらフローティングCTAを表示させる」といったケースで最近使いました。
あとは「ターゲット要素がスクロールしてビューポートに完全に入った時、非同期で検索結果を追加読込する」といった無限スクロール機能の開発でも活躍しました。
コード例では、分かりやすいケースとして前者を紹介してみたいと思います。

コード例1

HTMLでは以下のような感じで、フッターも含めたfloating-areaクラスの要素をターゲットとして、この要素がビューポートに表示されたらfloat-ctaクラスのCTAボタンの要素があるという構成になっています。

<div class="floating-area"><footer class="footer"><div class="footer__cta float-cta">
      <a href="#" class="float-cta__link">CTAボタン</a>
     </div>
    </footer>
</div><!-- .floating-area -->

そこで、ビューポートに交差したらCTAボタンにshowクラスを付与→交差しなくなったら(つまりfloating-areaクラスの要素までビューポートが到達していない状態なら)showクラスを削除…という処理をIntersectionObserverで書くと、こんな感じになります。

document.addEventListener("DOMContentLoaded", function () {
  // フローティングCTA
  // 表示の通過監視
  const callback = (entries) => {
    entries.forEach((entry) => {
      const ctaButton = document.querySelector('.float-cta');
      if ( entry.isIntersecting ) {
        ctaButton.classList.add('show');
      } else {
        ctaButton.classList.remove('show');
      }
    });
  };

  // オプション設定
  const options = {
    root: null,
  }

  const observer = new IntersectionObserver(callback, options);
  const target = document.querySelector('.floating-area');

  observer.observe(target);
});

optionsオブジェクトでroot: nullを指定してビューポートをルート要素としていますが、rootがない場合や、そもそもoptionsオブジェクトを渡さないパターンでもビューポートをルートとして機能しますね。

上記の例だとターゲットであるfloating-areaクラスがフッターまで続いているので、画面の一番下までCTAがフローティングしてくれる挙動となりますが、以下のHTMLのように

<div class="floating-area"></div>
<div class="other-area"></div>
<footer class="footer"><div class="footer__cta float-cta">
  <a href="#" class="float-cta__link">CTAボタン</a>
 </div>
</footer>

という構成だと、floating-areaクラスの要素が完全にページ上部に抜けると同時にCTAのshowクラスが外されて非表示になるという挙動となります。

コード例2

IntersectionObserverは一つで複数の対象要素を監視可能です。

  const ctaElements = document.querySelectorAll('.cta__button-link');

  const ctaCallback = ((entries, observer) => {
    entries.forEach(entry => {
      if ( entry.isIntersecting ) {
        entry.target.classList.add('show');
        observer.unobserve(entry.target);
      } 
    });
  });

  const ctaObserver = new IntersectionObserver( ctaCallback );

  ctaElements.forEach(cta => {
    ctaObserver.observe(cta);
  });

スクロールアニメーションの例ですね。
このような感じで書くと、複数のcta__button-linkクラス要素に対し、ビューポートに入った時にshowクラスを付与する処理が実装できます。

コールバック関数の中でobserverを引数として渡し、最後にunobserveメソッドを使っています。
こうすると交差時にクラス付与が終わったらその要素の監視を停止→無駄な監視をさせないようにできて、パフォーマンスの向上に一役買ってくれます。

使う時に注意したいこと

ターゲット要素にCSSのdisplay: none;を使っていると監視しないので注意が必要です。
要素が存在しないということで、監視の対象外となります。

  • visibility: hidden;
  • opacity: 0;

これらを使うと、要素自体は存在しているということで監視してくれます。

optionsオブジェクトについて

IntersectionObserverに設定できるオプションは3つあります。

root

ターゲットが見えるか確認するためのビューポート要素を指定できます。
これがnullや指定されなかった場合は、ブラウザのビューポートがデフォルトで設定されます。
別の要素をビューポート要素として設定したい場合は

const options = {
  root: document.querySelector("#target"),
};

このように書けばOKです。
ブラウザのビューポートに変わって、指定要素とターゲットが交差したかを監視してくれます。

rootMargin

rootに対してマージン設定ができます。
CSSのmarginと似たように値を設定でき、pxと%で記述可能です。

例えば

const options = {
  rootMargin: "100px 0px", // 上下100px
};

こうすると、スクロール時にターゲットの交差前100pxで先読みして処理するなどの使い方ができます。

逆にネガティブ値を設定することも可能で

const options = {
  rootMargin: "-100px 0px", // 上下-100px
};

スクロール時にターゲットと交差後100pxで処理するという内容になります。
処理位置を調整したい時に使えるオプションです。

threshold

ターゲットがどのくらいの割合で見えていれば、コールバック処理するかを設定できます。
0.0-1.0の間で割合指定ができ、単一の値か配列を設定できます。
デフォルトは0(1ピクセルでも交差したら処理)です。

分かりやすい例だと

const options = {
  threshold: 1.0,
};

こうすると、ターゲット要素が全て見えている状態でないと処理しません。
上記の値を0.5とかにすると、ターゲット全体の半分見えたら処理…という意味ですね。

配列のパターンだと、MDNにも紹介がありますが

const options = {
  threshold: [0, 0.25, 0.5, 0.75, 1],
};

ターゲットが25%ずつ表示される度に処理をする…という設定になります。

交差状態を取得

IntersectionObserverEntryという別のインターフェースから、交差状態について様々な情報を得られます。
IntersectionObserverのコールバック関数にentriesを引数として渡すと利用できます。

コード例でも使われているisIntersectingが交差したかどうかを真偽値で判定してくれています。
これを主に使用して、交差検知することになるかと思います。

ほかには、ビューポート内でターゲットがどのくらい交差しているかの比率をintersectionRatioで取得することもできますね。

CTAのコード例でいくと

  const ctaCallback = ((entries, observer) => {
    entries.forEach(entry => {
      if ( entry.isIntersecting ) { // 交差したかを判定
        entry.target.classList.add('show');
        if ( entry.intersectionRatio >= 0.5 ) { ターゲットが半分ビューポートに表示
          entry.target.classList.add('ratio-over');
        }
        observer.unobserve(entry.target);
      } 
    });
  });

こんな感じで、交差した瞬間にはshowクラスが、ターゲットの高さの半分までスクロールが進んだらratio-overクラスを付与する…という挙動となります。

詳しい情報やコードサンプルは、MDNのページで確認できますので、是非ご覧下さい。
https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API

使ってみて

JSプラグインやjQueryの煩雑な記述を利用せずに直感的にスクロールアニメーション用の処理を記述できるという実感がありました。
シンプルにisIntersectingでの交差状態を使った方法だけでも覚えると、かなり楽になりますね。
このインターフェースを知ってからは、スクロールに関する処理はほぼこれを使って実装しています。

終わりに

Web API・インターフェースの中では結構使用頻度の高い方ではないかと思います。
もしスクロール関連処理が出てきたら、真っ先に実装の選択肢として挙げられるように覚えておきたいですね。

次回もお楽しみに!

Discussion