--- title: Intersection Observer API slug: Web/API/Intersection_Observer_API tags: - API - Clipping - Intersection - Intersection Observer API - IntersectionObserver - Overview - Performance - Reference - Web - 交差 - 交差監視 translation_of: Web/API/Intersection_Observer_API ---
Intersection Observer API (交差監視 API) は、ターゲットとなる要素が、祖先要素もしくは文書の最上位の{{Glossary("viewport", "ビューポート")}}と交差する変更を非同期的に監視する方法を提供します。
従来、要素の可視性や、二つの要素間で互いに相対的な可視性を検出することは難しく、どの解決方法も不確実であり、ブラウザーやユーザーがアクセスするサイトの反応を鈍くする要因の一つとなっていました。ウェブが成熟していくにつれてこのような情報の必要性は高まっていきます。 Intersection (要素間交差) についての情報は下記のような理由から必要とされています。
これまで、要素間の交差を検出する実装をするには、 {{domxref("Element.getBoundingClientRect()")}} のようなメソッドを呼び出すイベントハンドラーやループがあり、影響を受ける要素に対する情報を都度計算し集めることで構成されていました。このようなコードがメインスレッドで実行されると、いずれかはパフォーマンスの問題を引き起こす可能性があります。試しにサイトにテストとして読み込めば分かりますが、事態は完全に酷くなりえます。
ウェブページで無限スクロールを使用することを考えてみてください。ベンダーから提供されるライブラリを使用して、ページ全体に定期的に配置された広告を管理し、アニメーショングラフィックスを表示し、通知ボックスなどを描画するカスタムライブラリを使用します。これらのそれぞれには独自の Intersection を検出するためのルーチンがあり、すべてがメインスレッド上で実行されます。ウェブサイトの作者は、これが起こっていることを認識していないかもしれません。なぜなら、彼らは内部の働きについてほとんど知らずに2つのライブラリを使用しているからです。ユーザーがページをスクロールすると、スクロール処理中にこれらの Intersection の検出ルーチンが絶えず起動し、ユーザーはブラウザー、ウェブサイト、およびコンピュータにイライラさせられることになります。
Intersection Observer API を使用することで、監視したい要素が別の要素 (もしくは{{Glossary("viewport")}}) に入ってきたり出ていったりする時、まだ両要素が交差する量がある一定の量を満たす時、実行されるコールバック関数を登録するが出来ます。こういった方法を用いることで、この手の要素交差を監視するためにサイトはメインスレッド上で何もする必要がなくなり、ブラウザーは要素間交差の管理を最適化して自由に行えます。
Intersection Observer API を使用してできないものの1つは、重複するピクセル数または具体的なピクセル数です。ただし、「N%前後のどこかで交差する場合に何かしたい」という一般的な利用法はカバーされています。
Intersection Observer API を使用すると、ある要素が、これをターゲットと呼びますが、端末のビューポートまたは指定された要素 - API の目的からこれをルート要素もしくはルートと呼びます - と交差するたびに呼び出されるコールバックを構成することができます。通常は、要素の直近のスクロール可能な祖先、または、要素がスクロール可能な要素の子孫でない場合はビューポートに関する交差状態の変更を監視したいでしょう。ルート要素に関する交差を監視するには、 null を指定してください。
ビューポートとその他の要素のどちらがルートとして使用されていても、 API は同じように動作し、ターゲット要素の表示状態が変わってルートとの間で交差の量の期待値を通るたびに、提供したコールバック関数が実行されます。
ターゲット要素とそのルート要素の交差する度合いが交差率です。これはターゲット要素のパーセンテージを 0.0 から 1.0 の間の値で表現したものです。
コンストラクターを呼び出して Intersection observer を作成し、閾値が一方向また他の方向に交差する度に実行されるコールバック関数を渡します。
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
1.0 の閾値は、 root オプションで指定された要素内でターゲットが100%表示された時にコールバックが呼び出されることを意味しています。
{{domxref("IntersectionObserver.IntersectionObserver", "IntersectionObserver()")}} コンストラクタに渡された options オブジェクトは、オブザーバーのコールバックが呼び出される状況を制御し、以下のフィールドがあります:
rootnull の場合はデフォルトでブラウザーのビューポートが使用されます。rootMargin10px 20px 30px 40px" (top, right, bottom, left) のようなものです。この値はパーセント値にすることができます。この一連の値は、交差を計算する前にルート要素の範囲のボックスの各辺を拡大または縮小させることができます。既定ではすべてゼロです。thresholdオブザーバーを作成した後は、監視するターゲット要素を与える必要があります。
var target = document.querySelector('#listItem');
observer.observe(target);
ターゲットが IntersectionObserver に指定された閾値を満たす度にコールバックが呼び出されます。コールバックは {{domxref("IntersectionObserverEntry")}} オブジェクトのリストとオブザーバーを受け取ります。
let callback = (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
});
};
コールバックはメインスレッドで実行される点に注意してください。可能な限り早く動作する必要があります。もし時間を要する処理であるなら、 {{domxref("Window.requestIdleCallback()")}} を使ったほうがいいでしょう。
また root オプションを指定した場合、target はルート要素の子要素でなければなりません。
Intersection Observer API によって考慮される領域は全て矩形です。不規則に整形された要素は、要素全てを囲む最小の矩形で占有しているとみなされます。同様に、要素の可視部分が矩形ではない場合、要素が交差する矩形は要素全ての可視部分を含む最小の矩形であると解釈されます。
{{domxref("IntersectionObserverEntry")}} オブジェクトによって提供される様々なプロパティがどのように交差を表現しているかを知るともっと役に立つでしょう。
要素とその入れ物との交差を監視するには、入れ物をまずは知る必要があります。ここでの入れ物とは交差ルートもしくはルート要素です。これは監視される要素の親要素となる文書内の特定の要素になるか、文書のビューポートを入れ物として使用する際は null になるかいずれかになります。
ルート交差矩形はターゲットをチェックするために使用される矩形です。この矩形は次のように決まります。
交差するルートとして使用される矩形は、ルートマージン rootMargin を {{domxref("IntersectionObserver")}} の作成時に設定することで調整することが可能です。 rootMargin の値は交差するルートの境界線各辺にオフセット追加定義して、最終的な交差のルートの境界線を作成します (コールバックが実行された際には {{domxref("IntersectionObserverEntry.rootBounds")}} で取得できるものです)。
Intersection Observer API はターゲット要素がどのくらい見えているのか微細な変化を全て知らせるのではなく、閾値を使用します。オブザーバーを作成する際に、表示されるターゲット要素がどの程度見えているかのパーセンテージを表す1つ以上の数値を指定できます。API はこれらの閾値を超えて見えたかどうかの変更のみを知らせます。
例えば、ターゲット要素が25%見える度に通知を受けたい場合は、オブザーバーを作成する際の閾値のリストとして [0, 0.25, 0.5, 0.75, 1] という配列を指定します。変更の通知を受ける時にコールバック関数に渡された {{domxref("IntersectionObserverEntry")}} の {{domxref("IntersectionObserverEntry.isIntersecting", "isIntersecting")}} プロパティの値をチェックすることで、変更が感知された方向 (つまり要素が見えたかどうかを) 判断することが出来ます。isIntersecting が true であれば、ターゲットは閾値を超えて少なくとも見るようになったということですし、false であればターゲットは指定した閾値では表示されなくなったということです。
閾値の仕組みを感じ取るには、下のボックスをスクロールして見てください。その中にある各色のボックスには四隅全てにパーセンテージが表示されています。そのため、入れ物をスクロールする時にこれらのパーセンテージが変化することが分かります。各ボックスには異なる閾値がセットされています:
[0.00, 0.01, 0.02, ..., 0.99, 1.00] となります。<template id="boxTemplate">
<div class="sampleBox">
<div class="label topLeft"></div>
<div class="label topRight"></div>
<div class="label bottomLeft"></div>
<div class="label bottomRight"></div>
</div>
</template>
<main>
<div class="contents">
<div class="wrapper">
</div>
</div>
</main>
.contents {
position: absolute;
width: 700px;
height: 1725px;
}
.wrapper {
position: relative;
top: 600px;
}
.sampleBox {
position: relative;
left: 175px;
width: 150px;
background-color: rgb(245, 170, 140);
border: 2px solid rgb(201, 126, 17);
padding: 4px;
margin-bottom: 6px;
}
#box1 {
height: 200px;
}
#box2 {
height: 75px;
}
#box3 {
height: 150px;
}
#box4 {
height: 100px;
}
.label {
font: 14px "Open Sans", "Arial", sans-serif;
position: absolute;
margin: 0;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.7);
width: 3em;
height: 18px;
padding: 2px;
text-align: center;
}
.topLeft {
left: 2px;
top: 2px;
}
.topRight {
right: 2px;
top: 2px;
}
.bottomLeft {
bottom: 2px;
left: 2px;
}
.bottomRight {
bottom: 2px;
right: 2px;
}
let observers = [];
startup = () => {
let wrapper = document.querySelector(".wrapper");
// Options for the observers
let observerOptions = {
root: null,
rootMargin: "0px",
threshold: []
};
// An array of threshold sets for each of the boxes. The
// first box's thresholds are set programmatically
// since there will be so many of them (for each percentage
// point).
let thresholdSets = [
[],
[0.5],
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
[0, 0.25, 0.5, 0.75, 1.0]
];
for (let i=0; i<=1.0; i+= 0.01) {
thresholdSets[0].push(i);
}
// Add each box, creating a new observer for each
for (let i=0; i<4; i++) {
let template = document.querySelector("#boxTemplate").content.cloneNode(true);
let boxID = "box" + (i+1);
template.querySelector(".sampleBox").id = boxID;
wrapper.appendChild(document.importNode(template, true));
// Set up the observer for this box
observerOptions.threshold = thresholdSets[i];
observers[i] = new IntersectionObserver(intersectionCallback, observerOptions);
observers[i].observe(document.querySelector("#" + boxID));
}
// Scroll to the starting position
document.scrollingElement.scrollTop = wrapper.firstElementChild.getBoundingClientRect().top + window.scrollY;
document.scrollingElement.scrollLeft = 750;
}
intersectionCallback = (entries) => {
entries.forEach((entry) => {
let box = entry.target;
let visiblePct = (Math.floor(entry.intersectionRatio * 100)) + "%";
box.querySelector(".topLeft").innerHTML = visiblePct;
box.querySelector(".topRight").innerHTML = visiblePct;
box.querySelector(".bottomLeft").innerHTML = visiblePct;
box.querySelector(".bottomRight").innerHTML = visiblePct;
});
}
startup();
{{EmbedLiveSample("Threshold_example", 500, 500)}}
ブラウザーは次のように最終的な交差矩形を計算します。これはすべて行われることですが、交差がいつ発生するかを正確に把握するために、これらの手順を理解すると役立ちます。
overflow に visible 以外を設定すると、クリッピングが行われます。<iframe> に到達したら、交差矩形はフレームのビューポートに切り取られ、フレームの親要素が次のブロックとなり、交差ルートに向けて再帰が行われます。ターゲット要素がルート要素内で見えている範囲が可視量の閾値を通過したとき、 {{domxref("IntersectionObserver")}} オブジェクトのコールバックが実行されます。コールバックは、入力引数として交差したすべての閾値を示す IntersectionObserverEntry オブジェクトの配列を、また参照として IntersectionObserver オブジェクト自身を受け取ります。
閾値のリスト内のそれぞれの項目は、通過した閾値を説明する {{domxref("IntersectionObserverEntry")}} オブジェクトです。つまり、それぞれの項目は指定された要素がルート要素とどれだけ交差したのか、要素が交差したと言えるのかどうか、推移が発生した方向を示します。
以下のコードスニペットは、要素がルート要素と交差していない状態から少なくとも75%が交差した状態まで推移した回数を数え続けて表示します。
intersectionCallback(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
let elem = entry.target;
if (entry.intersectionRatio >= 0.75) {
intersectionCounter++;
}
}
});
}
IntersectionObserver コールバックへの入力として、または {{domxref("IntersectionObserver.takeRecords()")}} を呼び出すことによって、の2つです。この単純な例では、ターゲット要素の色と透明度を要素の可視性で変化させます。Intersection Observer API を利用した時間の絡んだ要素の可視性では、要素 (例えば広告など) セットがユーザーに表示される時間を測定し、統計を記録したり要素を更新したりしてその情報にユーザーどう反応したかを示す、より拡張性の高い具体例を見ることが出来るようでしょう。
この例における HTML は非常に短く、主な要素はターゲットとなるボックス (IDは "box" としました) とボックス内のコンテンツです。
<div id="box">
<div class="vertical">
Welcome to <strong>The Box!</strong>
</div>
</div>
この例では CSS はあまり重要ではありません。要素をレイアウトし{{cssxref("background-color")}} と {{cssxref("border")}} 属性を 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;
}
最後に、Intersection Observer API を使って何が出来るか、 JavaScript のコードを見ていきましょう。
まずは、いくつかの変数を準備してオブザーバーをインストールする必要があります。
const numSteps = 20.0;
let boxElement;
let prevRatio = 0.0;
let increasingColor = "rgba(40, 40, 190, ratio)";
let decreasingColor = "rgba(190, 40, 40, ratio)";
// Set things up
window.addEventListener("load", (event) => {
boxElement = document.querySelector("#box");
createObserver();
}, false);
セットアップした定数と変数は下記の通りです。
numStepsprevRatioincreasingColordecreasingColor{{domxref("EventTarget.addEventListener", "Window.addEventListener()")}} を呼び出して{{event("load")}} イベントのリスンを開始します。ページロードが完了すると、{{domxref("Document.querySelector", "querySelector()")}} を使用して ID "box" 要素への参照を取得し createObserver() メソッドを呼び出して Intersection Observer の設定・インストール処理を開始します。
createObserver() メソッドは新しい {{domxref("IntersectionObserver")}} を作成し、ターゲット要素の監視を開始するためにページが完全にロードされてから呼び出されます。
function createObserver() {
let observer;
let options = {
root: null,
rootMargin: "0px",
threshold: buildThresholdList()
};
observer = new IntersectionObserver(handleIntersect, options);
observer.observe(boxElement);
}
この関数ではオブザーバーの設定を含む options オブジェクトを設定することから始めます。ドキュメントビューポートに対してターゲット要素がどのくらい見えているかという変化を監視したいので、root は null にします。マージンは必要がないので、マージンオフセットである rootMargin 設定は "0px" と指定しています。これによって、オブザーバーは追加された (もしくは差し引かれた) スペースがなくてもターゲット要素の境界とビューポートの境界の交差点がどう変化するのか監視を開始することが出来ます。
視認率の閾値のリストである、thresholdは関数 buildThresholdList() によって構成されます。閾値のリストは、この例ではプログラムによって計算されています。その数が意図的に調整可能だからです。
options が用意できたら、新しいオブザーバーを作成、つまり{{domxref("IntersectionObserver.IntersectionObserver", "IntersectionObserver()")}} のコンストラクタを呼び出して、閾値をまたいだ際に呼ばれる関数 handleIntersect() を指定し、オプションを指定します。次に、返されたオブザーバーに対して {{domxref("IntersectionObserver.observe", "observe()")}} を呼び出し、必要なターゲット要素を渡します。
observer.observe() をそれぞれの要素に対して呼び出すことにより、ビューポートに対して交差し変化しているかを複数の要素から監視することが出来ます。
閾値のリストを作成する buildThresholdList() 関数は次のようになります。
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) が指定された結果、以下の閾値のリストが表示れます。
| # | 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 |
もちろん、閾値の配列をハードコードすることは可能ですし、よくやりがちなことです。しかし、この例では設定を追加することで粒度を調整する余地が残っています。
ブラウザーはターゲット要素 (このケースでは "box" というIDを持つ要素です) が表示されているか、またはどのくらい見えているかという比率が、閾値のリストにある値の1つをまたぐことを検出して、handleIntersect() が呼び出されます:
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 内にある {{domxref("IntersectionObserverEntry")}} について、entry の {{domxref("IntersectionObserverEntry.intersectionRatio", "intersectionRatio")}} が上昇しているかを調べます。上昇していればターゲットの {{cssxref("background-color")}} に increasingColor ("rgba(40, 40, 190, ratio)" だったことを思い出してください) の値をセットし、その際にその中にある "ratio" という文字列を entry が持つ intersectionRatio と置き換えます。その結果、色が変更されるだけでなく、ターゲット要素の透明度も変更されます。交差する比率が下がるに連れて、背景色のアルファ値が下がりより透明度の高い要素となります。
同様に、 intersectionRatio が下がっている場合は decreasingColor を文字列として使用し "ratio" という文字列を intersectionRatio でもって置き換えたあとに、要素の background-color として適用します。
最後に、交差する割合が上がっているか下がっているかを追跡するために、変数 prevRatio に現在の比率を代入しておきます。
以下がその結果内容です。ページを上下にスクロールして、ボックスの外観がどう変化するかを確認してみましょう。
{{EmbedLiveSample('A_simple_example', 400, 400)}}
より応用的な例はTiming element visibility with the Intersection Observer API のセクションを見てください。
| 仕様書 | 状態 | 備考 |
|---|---|---|
| {{SpecName('IntersectionObserver')}} | {{Spec2('IntersectionObserver')}} |
{{Compat("api.IntersectionObserver")}}