👆

Intersection Observer APIで実装する無限スクロール

2024/05/22に公開

こんにちは!
株式会社ラブグラフでエンジニアをしているけいすけと申します!
今回は JavaScript の Intersection Observer API を使って無限スクロールを実装する方法を紹介します。

Intersection Observer API とは

Intersection Observer API とは HTML 中の要素の見え具合をチェックして、指定した見え具合になったときに任意の処理を実行することができる API です。
これを使うと無限スクロールを簡単に実装できます。早速、サンプルを見てみましょう!

無限スクロールのサンプル(説明用)

下に Intersection Observer API を使った無限スクロールのサンプルを示します。API の使い方はこのあとに説明します。
ぜひ無限にスクロールしてみてください!

挙動は次のようになっています。

  • ol 要素の中に li 要素を5つ配置
  • ol 要素の後ろに JavaScript で div 要素を追加(「この要素の全体がビューポートに入ったらコールバックを呼ぶ」と書かれた赤い四角形)
  • この div 要素の 100% がビューポート内に入ったときに、ol 要素の中に li 要素を5つ追加

3つ目の「div 要素の 100% がビューポート内に入ったときに」という部分で Intersection Observer API が活躍しています。
挙動を分かりやすく見せるために ol 要素の後ろに赤い四角形を表示していますが、最後にこの赤い四角形を非表示にしたサンプルも載せています。

ソースコード

このサンプルのソースコードを下に示します。

HTML

<ol>
  <li>list item 1</li>
  <li>list item 2</li>
  <li>list item 3</li>
  <li>list item 4</li>
  <li>list item 5</li>
</ol>

CSS

/* オレンジの四角形 */
li {
  height: 100px;
  margin: 20px;
  line-height: 100px;
  background-color: orange;
  color: white;
  text-align: center;
}

/* 赤の四角形 */
ol + div {
  height: 200px;
  line-height: 200px;
  background-color: red;
  color: white;
  text-align: center;
}

JavaScript

const list = document.querySelector('ol');
const observedElement = document.createElement('div');
list.insertAdjacentElement('afterend', observedElement); // ol 要素の後ろに div 要素を追加
observedElement.textContent = 'この要素の 100% がビューポートに入ったら li 要素を5つ追加';

const callback = (entries) => {
  // li 要素の数を取得
  const numberOfListItems = list.getElementsByTagName('li').length;
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // ol 要素の中に li 要素を5つ追加
      for (let i = 1; i <= 5; i++) {
        const listItem = document.createElement('li');
        listItem.textContent = `list item ${numberOfListItems + i}`;
        list.appendChild(listItem);
      }
    }
  });
};

const options = {
  root: null,
  rootMargin: "0px",
  threshold: 1.0,
};

const observer = new IntersectionObserver(callback, options);
observer.observe(observedElement);

Intersection Observer API の使い方

Intersection Observer API は次の手順で使います。

  1. callbackoptions を設定する
  2. IntersectionObserver コンストラクターに callbackoptions を渡して IntersectionObserver オブジェクトを作る
  3. 作ったオブジェクトの observe メソッドに監視したい要素を渡す

callback

監視する要素のリストを引数に持つ関数です。各要素は isIntersecting というプロパティを持っていて、このプロパティは options で指定された条件が満たされているときは true に、それ以外の場合は false になります。
上のサンプルの場合は監視している要素は赤い四角形(observedElement)だけなので、callback の引数は [observedElement] になっています。

次のように IntersectionObserver オブジェクトに監視する要素を2つ渡した場合は、callback の引数は [observedElement1, observedElement2] になります。

observer.observe(observedElement1);
observer.observe(observedElement2);

options

callback が呼ばれるタイミングを制御するためのオブジェクトで次の3つのプロパティを持っています。

root

監視している要素がどのくらい見えているかを計算するためのビューポートとして使う要素です。
指定されなかった場合または null の場合はブラウザのビューポートになります。
上のサンプルでは null を指定したので、赤い四角形がブラウザのビューポート内にどれくらい見えているかを監視していたことになります。

rootMargin

root の周りのマージンです。CSS の margin と同じように "10px 20px 30px 40px" (上、右、下、左)という具合に指定します。px ではなく % でも指定できます。デフォルト値は "0px 0px 0px 0px" です。
例えば、 "0px 0px 500px 0px" とした場合、root が下に 500px 拡大されます。root がブラウザのビューポートだった場合、ビューポートの下 500px の領域にある要素も見えていると判定されます。

threshold

監視している要素がどのくらい見えたときにコールバックを呼ぶか指定する値です。
0 以上 1 以下の単一の数値または 0 以上 1 以下の数値の配列です。

1 を指定した場合は監視している要素の 100% が見えたとき、0.5 を指定した場合は 50% が見えたとき、0 を指定した場合は監視している要素が 1px でも見えたらコールバックを呼びます。[0, 0.25, 0.5, 0.75, 1] を指定した場合は、見える割合が 25% を超える度にコールバックが呼ばれます。デフォルト値は 0 です。

Intersection Observer API で一番難しいのはこの callbackoptions の使い方だと思います。使い方は説明しましたがこれだけを読んで理解するのはなかなか難しいと思うので、サンプルのソースコードを編集して挙動がどう変わるかぜひ見てみてください。

無限スクロールの実用的なサンプル

上の例では ol 要素の後ろに div 要素を追加して、挙動を分かりやすく見せるために赤い四角形を表示していました。
実際に無限スクロールを実装するときに上のサンプルのような赤い四角形を表示したくはないと思うので、非表示にしたサンプルを下に示します。

ソースコード

ソースコードも貼っておきます。説明用のサンプルとの違いは、ol 要素の後ろに追加した div 要素のスタイルを消して赤い四角形を非表示にした点と、optionsthreshold1.0 から 0.0 にした点だけです。
こうすることで ol 要素の後ろにある div 要素(幅も高さもない)が 1px でも見えたら li 要素を追加するようになっています。

CSS

/* オレンジの四角形 */
li {
  height: 100px;
  margin: 20px;
  line-height: 100px;
  background-color: orange;
  color: white;
  text-align: center;
}

/* 赤の四角形のスタイルは削除 */

JavaScript

const list = document.querySelector('ol');
const observedElement = document.createElement('div');
list.insertAdjacentElement('afterend', observedElement);

const callback = (entries) => {
  const numberOfListItems = list.getElementsByTagName('li').length;
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      for (let i = 1; i <= 5; i++) {
        const listItem = document.createElement('li');
        listItem.textContent = `list item ${numberOfListItems + i}`;
        list.appendChild(listItem);
      }
    }
  });
};

const options = {
  root: null,
  rootMargin: "0px",
  threshold: 0.0, // 1.0 から 0.0 に変更
};

const observer = new IntersectionObserver(callback, options);
observer.observe(observedElement);

最後に

ここまで読んでいただきありがとうございます!
無限スクロールを実装する機会があったらぜひ Intersection Observer API を使ってみてください。

参考

https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

https://qiita.com/Kaitou/items/046d5b43eb6d798a87dd

ラブグラフのエンジニアブログ

Discussion