交差オブザーバー API

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2019.

交差オブザーバー API (Intersection Observer API) は、ターゲットとなる要素が、祖先要素または文書の最上位のビューポートと交差する変化を非同期的に監視する方法を提供します。

従来、ある要素の可視状態、あるいは 2 つの要素の相対的な可視状態を検出することは困難であり、その解決策は信頼性が低く、ブラウザーやアクセスするサイトの動作が重くなる傾向がありました。ウェブが成熟していくにつれて、このような情報の必要性は高まっていきます。交差情報 (Intersection information) についての情報は次の理由から必要とされています。

  • ページがスクロールした際の画像やその他のコンテンツの遅延読み込み。
  • 「無限スクロール」をするウェブサイトを実装し、スクロールに従って次々とコンテンツを読み込んで、ユーザーがページの切り替えをせずに済むようにすること。
  • 広告費を計算するための広告が表示されたかどうかのレポート。
  • ユーザーが結果を見るかどうかで、タスクを実行するかどうか、アニメーションを処理するかどうかを決定すること。

以前は、要素同士の交差の検出を実装するには、イベントハンドラーやループで Element.getBoundingClientRect() などのメソッドを呼び出し、影響を受けるすべての要素について必要な情報を蓄積していました。このコードはすべてメインスレッドで実行されるため、これらのうち 1 つでもあればパフォーマンスの問題を引き起こす可能性があります。サイトでこのような検出が大量に行われると、まったく醜くなる可能性があります。

ウェブページで無限スクロールを使用することを考えてみてください。ベンダーから提供されるライブラリーを使用して、ページ全体に定期的に配置された広告を管理し、アニメーショングラフィックスを表示し、通知ボックスなどを描画するカスタムライブラリーを使用します。それぞれには独自に交差を検出するためのルーチンがあり、すべてがメインスレッド上で実行されます。ウェブサイトの作者は、これが起こっていることを認識していないかもしれません。内部の働きについてほとんど知らずに 2 つのライブラリーを使用しているからです。ユーザーがページをスクロールすると、スクロール処理中にこれらの交差の検出ルーチンが絶えず起動し、ユーザーはブラウザー、ウェブサイト、およびコンピューターにイライラさせられることになります。

交差オブザーバー API は、特定の要素が他の要素(またはビューポート)と交差に入ったり出たりしたとき、または 2 つの要素間の交差量が指定された量だけ変化したときに実行されるコールバック関数を、コードが登録できるようにします。こうすることで、サイトはこのような要素の交差を監視するために、メインスレッドで何らかの処理をする必要がなくなり、ブラウザーは適切と思われる交差の管理を自由に最適化することができます。

交差オブザーバー API は、重複したピクセルの正確な数や、それがどのピクセルであるかを具体的に示すことはできません。しかし、「N % 前後のどこかで交差していたら、何かをする必要がある」という、より一般的な利用法はカバーします。

交差オブザーバーの概念と使い方

交差オブザーバー API を使用すると、以下のいずれかの状況が発生したときに呼び出されるコールバックを構成することができます。

  • ターゲット要素が端末のビューポートまたは指定された要素と交差したとき。この指定された要素は、交差オブザーバー API の用途ではルート要素またはルートと呼びます。
  • オブザーバーが対象要素を監視するよう最初に指示されたとき。

通常、対象要素の最も近いスクロール可能な祖先、または対象要素がスクロール可能な要素の子孫でない場合は、端末のビューポートを基準にして交差の変化を監視したいと思うでしょう。端末のビューポートを基準にして交差を監視するには、 root オプションに null を指定します。交差オブザーバーのオプションについてのより詳しい説明は、このまま読み進めてください。

ビューポートとその他の要素のどちらがルートとして使用されていても、 API は同じように動作し、対象要素の表示状態が変わってルートとの間で交差の量の期待値を通るたびに、提供したコールバック関数が実行されます。

対象要素とそのルート要素の交差する度合いが交差率です。これは対象要素のパーセント値を 0.0 から 1.0 の間の値で表現したものです。

交差オブザーバーの作成

交差オブザーバーは、コンストラクターを呼び出して閾値が一方向また他の方向に交差する度に実行されるコールバック関数を渡すことで生成します。

js
let options = {
  root: document.querySelector("#scrollArea"),
  rootMargin: "0px",
  threshold: 1.0,
};

let observer = new IntersectionObserver(callback, options);

閾値 (threshold) の 1.0 は、 root オプションで指定された要素内でターゲットが 100% 表示された時にコールバックが呼び出されることを意味しています。

交差オブザーバーのオプション

options オブジェクトは IntersectionObserver() コンストラクターに渡され、オブザーバーのコールバックが呼び出される状況を制御します。以下のようなフィールドがあります。

root

ターゲットが見えるかどうかを確認するためのビューポートとして使用される要素です。指定されなかった場合、または null の場合は既定でブラウザーのビューポートが使用されます。

rootMargin

root の周りのマージンです。 CSS の margin プロパティに似た値を指定することができます。例えば、 "10px 20px 30px 40px" (上、右、下、左)のようにします。この値はパーセント値にすることができます。この一連の値は、交差を計算する前にルート要素の範囲のボックスの各辺を拡大または縮小させることができます。既定値はすべてゼロです。

threshold

単一の数値または数値の配列で、ターゲットがどのくらいの割合で見えている場合にオブザーバーのコールバックを実行するかを示します。見える範囲が 50% を超えたときのみ検出する場合は値 0.5 を使用します。 25% を超える度にコールバックを実行する場合は、 [0, 0.25, 0.5, 0.75, 1] という配列を指定します。既定値は 0 です(つまり、 1 ピクセルでも表示されるとコールバックが実行されます)。 1.0 の値は全てのピクセルが見えるようになるまで、閾値を超えたとはみなされないことを意味します。

監視される要素をターゲットにする

オブザーバーを作成した後は、監視する対象要素を与える必要があります。

js
let target = document.querySelector("#listItem");
observer.observe(target);

// オブザーバーに設定したコールバックが初めて実行され、オブザーバーにターゲットが
// 割り当てられるまで待機します(ターゲットが現在表示されていない場合でも同様)。

ターゲットが IntersectionObserver に指定された閾値を満たす度にコールバックが呼び出されます。コールバックは IntersectionObserverEntry オブジェクトのリストとオブザーバー自体を受け取ります。

js
let callback = (entries, observer) => {
  entries.forEach((entry) => {
    // それぞれの項目は、観測された 1 つの対象要素の交差状態の変化を示している。
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

コールバックが受信した項目のリストには、交差状態の変化を報告する各ターゲットに対して 1 つずつの項目が含まれます。 isIntersecting プロパティの値をチェックして、その項目が現在ルートと交差している要素を表しているかどうかを確認します。

コールバックはメインスレッドで実行される点に注意してください。可能な限り早く動作する必要があります。もし時間を要する処理であるなら、 Window.requestIdleCallback() を使ったほうがいいでしょう。

また root オプションを指定した場合、ターゲットはルート要素の子孫でなければなりません。

交差の計算方法

交差オブザーバー API によって考慮される領域はすべて矩形です。異形の要素は、要素全体を囲む最小の矩形で占有しているとみなされます。同様に、要素の可視部分が矩形ではない場合、要素が交差する矩形は要素の可視部分全体を含む最小の矩形であると見なされます。

IntersectionObserverEntry オブジェクトによって提供される様々なプロパティが、どのように交差を表現するかを理解すると有益でしょう。

交差ルートとルートマージン

ある要素とそのコンテナーとの交差を監視するまえに、まずはコンテナーを知る必要があります。ここでのコンテナーとは交差ルートまたはルート要素です。これは監視される要素の親要素となる文書内の特定の要素になるか、文書のビューポートをコンテナーとして使用する際は null になるかいずれかになります。

ルート交差矩形はターゲットをチェックするために使用される矩形です。この矩形は次のように決まります。

  • 交差ルートが暗黙のルート(すなわち最上位の Document)である場合、ルート交差矩形はビューポートの矩形になります。
  • 交差ルートのあふれた部分が切り取られていた場合、ルート交差矩形はルート要素のコンテンツ領域になります。
  • それ以外の場合は、ルート交差矩形は交差ルートのクライアント矩形(getBoundingClientRect() を呼び出して返されるもの)です。

交差するルートとして使用される矩形は、ルートマージン rootMarginIntersectionObserver の作成時に設定することで調整することが可能です。 rootMargin の値は交差ルートの外接ボックスの各辺に追加されるオフセットを定義して、最終的な交差ルートの境界を作成します(コールバックが実行された際には IntersectionObserverEntry.rootBounds で取得できるものです)。

閾値

交差オブザーバー API は、対象要素が見える量の微細な変化が発生するたびに知らせるのではなく、閾値 (threshold) を使用します。オブザーバーを作成する際に、表示される対象要素がどの程度見えているかの割合を表す 1 つ以上の数値を指定することができます。API はこれらの閾値を超えて見えたかどうかの変更のみを知らせます。

例えば、対象要素が見える量が 25% 単位で増えたり減ったりするたびに通知を受けたい場合は、オブザーバーを作成する際に [0, 0.25, 0.5, 0.75, 1] という配列を閾値のリストとして指定します。

コールバックが呼び出されると、 IntersectionObserverEntry オブジェクトのリストを受け取ります。これは、ルートと交差する度合いが変化し、露出量がいずれかの方向に閾値を越えた観測対象ごとに 1 つずつあります。

ターゲットが現在ルートと交差しているかどうかは、項目の isIntersecting プロパティを見ることで確認できます。これにより、その項目が、要素が交差している状態から交差しなくなるまでの遷移を表すのか、交差していない状態から交差する状態への遷移を表すのかを判断することができます。

交差する矩形がゼロになることもあり得ることに注意してください。これは、交差部分が両者の境界線にぴったり沿っているか、または boundingClientRect の面積がゼロの場合に起こり得ることです。このようにターゲットとルートが境界線を共有している状態は、交差した状態に遷移したとみなすには不十分です。

閾値の仕組みを感じ取るには、下のボックスをスクロールして見てください。その中にある各色のボックスには四隅全てにパーセント値が表示されています。コンテナーをスクロールする時にこれらのパーセント値が変化することが分かります。各ボックスには異なる閾値が設定されています。

  • 最初のボックスには、可視点の各パーセント値が設定されています。つまりIntersectionObserver.thresholds の配列は [0.00, 0.01, 0.02, /*…,*/ 0.99, 1.00] となります。
  • 2 つ目のボックスには単一の閾値が、 50% の位置にあります。
  • 3 つ目のボックスには、可視率 10% 毎に閾値があります (0%, 10%, 20%...)
  • 最後のボックスの閾値は 25% 毎です。

クリッピングと交差矩形

ブラウザーは次のように最終的な交差矩形を計算します。これはすべて完了した後の状態が見えますが、交差がいつ発生するかを正確に把握するために、これらの手順を理解すると役立ちます。

  1. 対象要素の外接矩形(つまり、要素を構成するすべてのコンポーネントの外接ボックスを完全に囲む最小の矩形)を、ターゲットに対して getBoundingClientRect() を呼び出すことによって取得します。これは、交差の矩形の最大の大きさです。残りの手順で、交差しない部分を削除していきます。
  2. ターゲットの直接の親ブロックから外側に向けて、それぞれの包含ブロックのクリッピングを(存在すれば)交差する矩形に適用します。ブロックのクリッピングは、 2 つのブロックの交差と、 overflow プロパティで(存在すれば)指定されたクリッピングモードに基づいて決定されます。 overflowvisible 以外を設定すると、クリッピングが行われます。
  3. 包含する要素の中に、ネストされた閲覧コンテキストのルートがあった場合(<iframe> に含まれる文書など)、交差する矩形は包含するコンテキストのビューポートで切り取られ、コンテナー群を通して上方に再帰的にコンテナーの包含ブロックを続けます。ですから、最上位の <iframe> に到達したら、交差矩形はフレームのビューポートに切り取られ、そのフレームの親要素が交差ルートに向けた再帰の次のブロックになります。
  4. 上方への再帰が交差ルートに達すると、結果の矩形が交差ルートの座標空間に対応付けられます。
  5. 結果の矩形はそれからルート交差矩形と交差することで更新されます。
  6. この矩形は、最終的に、ターゲットの document の座標空間に対応付けられます。

交差状態の変化のコールバック

対象要素がルート要素内で見えている範囲の可視量が閾値を通過したとき、 IntersectionObserver オブジェクトのコールバックが実行されます。コールバックは、入力引数として交差したすべての閾値を示す IntersectionObserverEntry オブジェクトの配列を、また参照として IntersectionObserver オブジェクト自身を受け取ります。

閾値のリスト内のそれぞれの項目は、通過した閾値を説明する IntersectionObserverEntry オブジェクトです。つまり、それぞれの項目は、指定された要素がルート要素とどの程度交差しているか、その要素が交差しているとみなされるかどうか、変化が発生した方向などを記述しています。

以下のコードスニペットでは、要素がルートと交差していない状態から 75% 以上交差した状態に遷移した回数をカウントするコールバックを示しています。閾値が 0.0 (既定値)の場合、コールバックはおよそ isIntersecting の論理値が変化した時に呼び出されます。このスニペットでは、まず遷移が正の値であることを確認し、次に intersectionRatio が 75% 以上かどうかを判断し、その場合はカウンターをインクリメントしています。

js
const intersectionCallback = (entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      let elem = entry.target;

      if (entry.intersectionRatio >= 0.75) {
        intersectionCounter++;
      }
    }
  });
};

インターフェイス

IntersectionObserver

交差オブザーバー API の主なインターフェイスです。同一の交差設定に対して任意の数の対象要素を監視するオブザーバーを作成し管理するためのメソッドを提供します。各オブザーバーは 1 つ以上の対象要素と共通の親要素、または最上位の Documentビューポートとの交差における変化を非同期的に監視することが出来ます。この親要素またはビューポートはルートと呼ばれます。

IntersectionObserverEntry

スクロールにおける変化の特定の瞬間において、対象要素とルートとなるコンテナーとの交差を表現します。この型のオブジェクトは、 IntersectionObserver コールバックへの入力、または IntersectionObserver.takeRecords() の呼び出しの 2 通りの方法でのみ受け取ることができます。

単純な例

この単純な例では、対象要素の色と透明度を要素の可視性で変化させます。交差オブザーバー API による要素の表示時間設定では、要素のセット(例えば広告など)がユーザーに表示される時間を測定し、統計を記録したり要素を更新したりしてその情報にユーザーどう反応したかを示す、より拡張性の高い具体例を見ることができるでしょう。

HTML

この例における HTML は非常に短く、主な要素はターゲットとなるボックス(IDは "box" としました)とボックス内のコンテンツです。

html
<div id="box">
  <div class="vertical">Welcome to <strong>The Box!</strong></div>
</div>

CSS

この CSS はこの例ではあまり重要ではありません。この CSS は要素をレイアウトし、 background-colorborder 属性が CSS トランジションに加わることができるようにし、要素が多少見えなくなるときの変更に影響を与えるのに使用します。

css
#box {
  background-color: rgba(40, 40, 190, 1);
  border: 4px solid rgb(20, 20, 120);
  transition:
    background-color 1s,
    border 1s;
  width: 350px;
  height: 350px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.vertical {
  color: white;
  font: 32px "Arial";
}

.extra {
  width: 350px;
  height: 350px;
  margin-top: 10px;
  border: 4px solid rgb(20, 20, 120);
  text-align: center;
  padding: 20px;
}

JavaScript

最後に、交差オブザーバー API を使って何ができるのか、 JavaScript のコードを見ていきましょう。

セットアップ

まずは、いくつかの変数を準備してオブザーバーをインストールする必要があります。

js
const numSteps = 20.0;

let boxElement;
let prevRatio = 0.0;
let increasingColor = "rgba(40, 40, 190, ratio)";
let decreasingColor = "rgba(190, 40, 40, ratio)";

// セットアップする
window.addEventListener(
  "load",
  (event) => {
    boxElement = document.querySelector("#box");

    createObserver();
  },
  false,
);

セットアップした定数と変数は下記の通りです。

numSteps

可視率が 0.0 から 1.0 の間にどのくらいの数の閾値を設定するか示す定数です。

prevRatio

この変数は閾値を超えた最後の可視率を記録するために使用します。これは対象要素が大体見えるようになったかどうかを調べることが出来ます。

increasingColor

可視率が増加している時に対象要素に適用する色を定義する文字列です。文字列の中の "比率" という単語は対象要素の現在の可視率に置き換えられ、要素が色を変化させるだけでなく不透明になるにつれて透明度が増していきます。

decreasingColor

同様に、可視率が減少していく時に適用する色を定義する文字列です。

Window.addEventListener() を呼び出して load イベントの待ち受けを開始します。ページの読み込みが完了すると、querySelector() を使用して ID が "box" 要素への参照を取得し、 createObserver() メソッドを呼び出して交差オブザーバーの設定とインストール処理を開始します。

交差オブザーバーの作成

createObserver() メソッドは新しい交差オブザーバー (IntersectionObserver) を作成し、対象要素の監視を開始するためにページが完全に読み込まれてから呼び出されます。

js
function createObserver() {
  let observer;

  let options = {
    root: null,
    rootMargin: "0px",
    threshold: buildThresholdList(),
  };

  observer = new IntersectionObserver(handleIntersect, options);
  observer.observe(boxElement);
}

この関数ではオブザーバーの設定を含む options オブジェクトを設定することから始めます。文書のビューポートに対して対象要素がどのくらい見えているかという変化を監視したいので、 rootnull にします。マージンは必要がないので、マージンオフセットである rootMargin 設定は "0px" と指定しています。これによって、オブザーバーは追加された(または差し引かれた)空間がなくても対象要素の境界とビューポートの境界の交差点がどう変化するのか監視を開始することができます。

可視率の閾値のリストである、 threshold は関数 buildThresholdList() によって構成されます。閾値のリストは、この例ではプログラムによって計算されています。その数が意図的に調整可能だからです。

options が用意できたら、新しいオブザーバーを作成、つまりIntersectionObserver() のコンストラクターを呼び出して、閾値をまたいだ際に呼ばれる関数 handleIntersect() を指定し、オプションを指定します。次に、返されたオブザーバーに対して observe() を呼び出し、必要な対象要素を渡します。

observer.observe() をそれぞれの要素に対して呼び出すことにより、ビューポートに対して交差し変化しているかを複数の要素から監視することが出来ます。

閾値比率の配列を組み立てる

閾値のリストを作成する buildThresholdList() 関数は次のようになります。

js
function buildThresholdList() {
  let thresholds = [];
  let numSteps = 20;

  for (let i = 1.0; i <= numSteps; i++) {
    let ratio = i / numSteps;
    thresholds.push(ratio);
  }

  thresholds.push(0);
  return thresholds;
}

これは 1 と numSteps の間の各整数 i に対して、値 i/numSteps を閾値の配列に入れることで、それぞれが 0.0 と 1.0 の間の比率である閾値の配列を作成しています。また、0 を配列に含めます。既定の numSteps (20) が指定された結果、以下の閾値のリストが表示されます。

# #
0 0.05 11 0.6
1 0.1 12 0.65
2 0.15 13 0.7
3 0.2 14 0.75
4 0.25 15 0.8
5 0.3 16 0.85
6 0.35 17 0.9
7 0.4 18 0.95
8 0.45 19 1
9 0.5 20 0
10 0.55

もちろん、閾値の配列をハードコードすることは可能ですし、よくやりがちなことです。しかし、この例では設定を追加することで粒度を調整したりするする余地が残っています。

交差の変換の処理

ブラウザーは対象要素(この場合は "box" という ID を持つ要素)が表示されているか、またはどのくらい見えているかという比率が、閾値のリストにある値の 1 つをまたぐことを検出して、 handleIntersect() を呼び出します。

js
function handleIntersect(entries, observer) {
  entries.forEach((entry) => {
    if (entry.intersectionRatio > prevRatio) {
      entry.target.style.backgroundColor = increasingColor.replace(
        "ratio",
        entry.intersectionRatio,
      );
    } else {
      entry.target.style.backgroundColor = decreasingColor.replace(
        "ratio",
        entry.intersectionRatio,
      );
    }

    prevRatio = entry.intersectionRatio;
  });
}

リストである entries 内にある IntersectionObserverEntry について、entry の intersectionRatio が上昇しているかを調べます。上昇していればターゲットの background-colorincreasingColor"rgba(40, 40, 190, ratio)" だったことを思い出してください)の値をセットし、その際にその中にある "ratio" という文字列を entry が持つ intersectionRatio と置き換えます。その結果、色が変更されるだけでなく、対象要素の透明度も変更されます。交差する比率が下がるに連れて、背景色のアルファ値が下がりより透明度の高い要素となります。

同様に、 intersectionRatio が下がっている場合は decreasingColor を文字列として使用し "ratio" という文字列を intersectionRatio でもって置き換えたあとに、要素の background-color として適用します。

最後に、交差する割合が上がっているか下がっているかを追跡するために、変数 prevRatio に現在の比率を代入しておきます。

結果

以下がその結果内容です。ページを上下にスクロールして、ボックスの外観がどう変化するかを確認してみましょう。

より応用的な例は交差オブザーバー API による要素の表示時間設定の節を見てください。

仕様書

Specification
Intersection Observer
# intersection-observer-interface

ブラウザーの互換性

BCD tables only load in the browser

関連情報