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

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

target 要素とその root 要素の交差する度合いが 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) のようなものです。root が指定されている場合、値はパーセンテージになります。この値がセットされることで、交差する計算をする前に root 要素のバウンディングボックスの各辺を拡大できたり縮小できたりします。デフォルトは全てゼロです。
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 は root 要素の子要素でなければなりません。

交差点をどう計算するか

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

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

交差する root と root margin

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

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

閾値

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

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

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

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

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

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

インターフェイス

IntersectionObserver
Intersection Observer API のプライマリーなインターフェイスです。同一の交差設定に対して任意の数の target 要素を監視するオブザーバを作成し管理するためのメソッドを提供します。各オブザーバは1つ以上の target 要素と共通の親要素、もしくは最上位のDocumentviewport との交差における変化を非同期的に監視することが出来ます。この親要素もしくはビューポートは root と呼ばれます。
IntersectionObserverEntry
スクロールにおける変化の特定の瞬間において、target 要素と root となる入れ物との交差を表現します。このタイプのオブジェクトは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-color と border 属性を CSS transitions に適用させます。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 要素がどのくらい見えているかという変化を監視したいので、root は null にします。マージンは必要がないので、マージンオフセットである rootMargin 設定は "0px" と指定しています。これによって、オブザーバは追加された(もしくは差し引かれた)スペースがなくても target 要素の境界とビューポートの境界の交差点がどう変化するのか監視を開始することが出来ます。

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

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

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

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

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

function buildThresholdList() {
  var thresholds = [];

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

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

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

結果

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

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

仕様

Specification Status Comment
Intersection Observer 草案  

ブラウザ実装状況

We're converting our compatibility data into a machine-readable JSON format. This compatibility table still uses the old format, because we haven't yet converted the data it contains. Find out how you can help!

Feature Chrome Edge Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
Basic support 51 15 55 (55)[1][2] 未サポート 38 WebKit bug 159475
Feature Android Webview Chrome for Android Firefox Mobile (Gecko) Firefox OS IE Mobile Opera Mobile Safari Mobile
Basic support 51 51 55.0 (55)[1][2] 未サポート 未サポート 38 WebKit bug 159475

[1] この機能は Gecko 53.0 (Firefox 53.0 / Thunderbird 53.0 / SeaMonkey 2.50) においてデフォルトでは false だった dom.IntersectionObserver.enabled の設定後に実装されています。実際には Firefox 55 からデフォルトで有効となります。 バグ 1243846 を参照してください。

[2] Firefox は現在、root 内の要素の可視性を計算する際に親要素の clip-path を考慮しません。この問題については バグ 1319140 を参照してください。

関連情報

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

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