これは実験的な機能です。本番で使用する前にブラウザー実装状況をチェックしてください。

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

従来、要素の出現を判別することや2つの要素間の相対的にどのような位置関係を持つか判別することは難しく、どの解決方法も不確実であり、ブラウザーやユーザーがアクセスするサイトの反応を鈍くする要因の一つとなっていました。残念なことに、ウェブが成熟していくにつれてこのような情報の必要性は高まっていきます。 Intersection (要素間交差) についての情報は下記のような理由から必要とされています。

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

これまで、要素間の交差を検出する実装をするには、 Element.getBoundingClientRect() のようなメソッドを呼び出すイベントハンドラーやループがあり、影響を受ける要素に対する情報を都度計算し集めることで構成されていました。このようなコードがメインスレッドで実行されると、いずれかはパフォーマンスの問題を引き起こす可能性があります。試しにサイトにテストとして読み込めば分かりますが、事態は完全に酷くなりえます。

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

Intersection Observer API を使用することで、監視したい要素が別の要素 (もしくはviewport) に入ってきたり出ていったりする時、まだ両要素が交差する量がある一定の量を満たす時、実行されるコールバック関数を登録するが出来ます。こういった方法を用いることで、この手の要素交差を監視するためにサイトはメインスレッド上で何もする必要がなくなり、ブラウザーは要素間交差の管理を最適化して自由に行えます。

Intersection Observer API を使用してできないものの1つは、オーバーラップするピクセル数または具体的なピクセル数です。ただし、「N%前後のどこかで交差する場合に何かしたい」という一般的なユースケースはカバーされています。

Intersection observer 概念と使い方

Intersection Observer API を使用すると、target と呼ばれる要素の1つがデバイスのビューポートまたは指定された要素 - API の目的からこれをルート要素もしくはルートと呼びます - と交差するたびにコールされるコールバック関数を構成できます。通常、ドキュメントのビューポートに注視して交差する変更 (ルート要素として null を指定することで行われます) を監視する必要があります。ビューポートやその他の要素をルートとして使用している場合でも、target 要素が見えるか見えないかが変更されてルートとの交差する量が期待値を前後するたびに、コールバック関数を実行して同じようにAPIが動作します。

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

intersection obserer の作成

コンストラクターを呼び出して intersection observer を作成し、閾値が一方向また他の方向に交差する度に実行されるコールバック関数を渡します。

var options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}

var observer = new IntersectionObserver(callback, options);

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

Intersection observer のオプション

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

root
target が見えるかどうかを確認するためのビューポートとして使用される要素です。指定されなかった場合、もしくは null の場合はデフォルトでブラウザーのビューポートが使用されます。
rootMargin
root の周りのマージンです。CSS margin プロパティに似た値を持つことができます。例えば、"10px 20px 30px 40px" (top, right, bottom, left) のようなものです。この値はパーセント値にすることができます。この一連の値は、交差を計算する前にルート要素の範囲のボックスの各辺を拡大または縮小させることができます。既定ではすべてゼロです。
threshold
1つの値もしくは値の配列で、オブザーバーのコールバックを実行する target がどのくらいの割合で見えているかを示します。50% 通過したときのみ検出する場合は値 0.5 を使用します。25% を超える度にコールバックを実行する場合は、 [0, 0.25, 0.5, 0.75, 1] といった配列を指定します。デフォルトは 0 です (つまり、1ピクセルでも表示されるとコールバックが実行されます)。1.0 の値は全てのピクセルが見えるようになるまで、閾値をまたいだとみなされないことを意味します。

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

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

var target = document.querySelector('#listItem');
observer.observe(target);

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

var callback = function(entries, observer) { 
  entries.forEach(entry => {
    // Each entry describes an intersection change for one observed
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

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

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

交差の計算方法

Intersection Observer API によって考慮される領域は全て矩形です。不規則に整形された要素は、要素全てを囲む最小の矩形で占有しているとみなされます。同様に、要素の可視部分が矩形ではない場合、要素が交差する矩形は要素全ての可視部分を含む最小の矩形であると解釈されます。

IntersectionObserverEntry オブジェクトによって提供される様々なプロパティがどのように交差を表現しているかを知るともっと役に立つでしょう。

交差するルートと root margin

要素とその入れ物との交差を監視するには、入れ物をまずは知る必要があります。ここでの入れ物とは交差ルートもしくはルート要素です。これは監視される要素の親要素となる文書内の要素になるか、文書のビューポートを入れ物として使用する際は null になるかいずれかになります。

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

閾値

Intersection Observer API は target 要素がどのくらい見えているのか微細な変化を全て知らせるのではなく、閾値 (thresholds) を使用します。オブザーバーを作成する際に、表示される target 要素がどの程度見えているかのパーセンテージを表す1つ以上の数値を指定できます。API はこれらの閾値を超えて見えたかどうかの変更のみを知らせます。

例えば、 target 要素が25%見える度に通知を受けたい場合は、オブザーバーを作成する際の閾値のリストとして [0, 0.25, 0.5, 0.75, 1] という配列を指定します。変更の通知を受ける時にコールバック関数に渡された IntersectionObserverEntryisIntersecting プロパティの値をチェックすることで、変更が感知された方向 (つまり要素が見えたかどうかを) 判断することが出来ます。isIntersectingtrue であれば、target は閾値を超えて少なくとも見るようになったということですし、false であれば target は指定した閾値では表示されなくなったということです。

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

  • 最初のボックスは可視点の各パーセンテージ値がセットされています; つまりIntersectionObserver.thresholds の配列は [0.00, 0.01, 0.02, ..., 0.99, 1.00] となります。
  • 2つ目のボックスは50%を指定した一つの閾値しか持ちません。
  • 3つ目のボックスは10%見える毎の閾値を持っています (0%, 10%, 20%...)
  • 最後のボックスは25%の閾値です。

変更が感知されたあとのコールバック

ルート要素内における target 要素の可視量が閾値と交差する場合、

インターフェイス

IntersectionObserver
Intersection Observer API のプライマリーなインターフェイスです。同一の交差設定に対して任意の数の target 要素を監視するオブザーバーを作成し管理するためのメソッドを提供します。各オブザーバーは1つ以上の target 要素と共通の親要素、もしくは最上位のDocumentviewport との交差における変化を非同期的に監視することが出来ます。この親要素もしくはビューポートはルートと呼ばれます。
IntersectionObserverEntry
スクロールにおける変化の特定の瞬間において、target 要素とルートとなる入れ物との交差を表現します。このタイプのオブジェクトは2つの方法でのみ得られます: IntersectionObserver コールバックへの入力として、または IntersectionObserver.takeRecords() を呼び出すことによって、の2つです。

単純な例

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

HTML

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

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

CSS

この例では CSS はあまり重要ではありません。要素をレイアウトしbackground-colorborder 属性を CSS トランジションに適用させます。CSS transitions は要素の変化に多少変化が起きることを確認するために使用します。

#box {
  background-color: rgba(40, 40, 190, 255);
  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

最後に、Intersection Observer API を使って何が出来るか、 JavaScript のコードを見ていきましょう。

セットアップ

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

var numSteps = 20.0;

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

// Set things up.

window.addEventListener("load", function(event) {
  boxElement = document.querySelector("#box");

  createObserver();
}, false);

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

numSteps
視認率が0.0から1.0の間にどのくらいの数の閾値を設定するか示す定数です。
prevRatio
この変数は閾値を超えた最後の視認率を記録するために使用します。これは target 要素が大体見えるようになったかどうかを調べることが出来ます。
increasingColor
視認率が増加している時に target 要素に適用する色を定義する文字列です。文字列の中の "比率" という単語は target 要素の現在の視認率に置き換えられ、要素が色を変化させるだけでなく不透明になるにつれて透明度が増していきます。
decreasingColor
同様に、視認率が減少していく時に適用する色を定義する文字列です。

Window.addEventListener() を呼び出してload イベントのリスンを開始します。ページロードが完了すると、querySelector() を使用して ID "box" 要素への参照を取得し createObserver() メソッドを呼び出して Intersection Observer の設定・インストール処理を開始します。

Intersection Observer の作成

createObserver() メソッドは新しい IntersectionObserver を作成し、target 要素の監視を開始するためにページが完全にロードされてから呼び出されます。

function createObserver() {
  var observer;

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

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

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

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

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

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

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

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

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

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

  thresholds.push(0);
  return thresholds;
}

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

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

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

交差の変換の処理

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

function handleIntersect(entries, observer) {
  entries.forEach(function(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 が上昇しているかを調べます; 上昇していれば target の background-colorincreasingColor ("rgba(40, 40, 190, ratio)" だったことを思い出してください) の値をセットし、その際にその中にある "ratio" という文字列を entry が持つ intersectionRatio と置き換えます。その結果、色が変更されるだけでなく、target 要素の透明度も変更されます。交差する比率が下がるに連れて、背景色のアルファ値が下がりより透明度の高い要素となります。

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

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

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

より応用的な例はTiming element visibility with the Intersection Observer API のセクションを見てください。

仕様書

仕様書 状態 備考
Intersection Observer 草案  

ブラウザーの対応

機能ChromeEdgeFirefoxInternet ExplorerOperaSafari
基本対応5115

55

53 — 551

なし ? ?
IntersectionObserver() constructor5115

55

53 — 551

なし ? ?
root5115

55

53 — 551

なし ? ?
rootMargin5115

55

53 — 551

なし ? ?
thresholds5115

55

53 — 551

なし ? ?
disconnect51152

55

53 — 551

なし ? ?
observe5115

55

53 — 551

なし ? ?
takeRecords51152

55

53 — 551

なし ? ?
unobserve51152

55

53 — 551

なし ? ?
機能Android webviewChrome for AndroidEdge mobileFirefox for AndroidOpera AndroidiOS SafariSamsung Internet
基本対応5151 あり ? ? ?5.0
IntersectionObserver() constructor5151 あり ? ? ?5.0
root5151 あり ? ? ?5.0
rootMargin5151 あり ? ? ?5.0
thresholds5151 あり ? ? ?5.0
disconnect5151 あり ? ? ?5.0
observe5151 あり ? ? ?5.0
takeRecords5151 あり ? ? ?5.0
unobserve5151 あり ? ? ?5.0

1. From version 53 until version 55 (exclusive): this feature is behind the dom.IntersectionObserver.enabled preference (needs to be set to true). To change preferences in Firefox, visit about:config.

2. Available since Windows Insider Preview Build 14986

関連情報

ドキュメントのタグと貢献者

このページの貢献者: mfuji09, tkdn, tanaka0325, kei-itof
最終更新者: mfuji09,