Intersection Observer v2 では、交差自体を検出するだけでなく、交差時に交差する要素が可視であったかどうかを検出する機能が追加されています。
Intersection Observer v1 は、おそらくすべての人に愛されている API の 1 つです。Safari でもサポートされるようになったため、すべての主要ブラウザで使用できるようになりました。API について簡単に復習するには、以下に埋め込まれている Intersection Observer v1 に関する Surma のSupercharged Microtip をご覧ください。Surma による詳細な記事もご覧ください。Intersection Observer v1 は、画像や動画の遅延読み込み、要素が position: sticky
に達したときに通知する、アナリティクス イベントを発生させるなど、さまざまなユースケースで使用されています。
詳細については、MDN の Intersection Observer のドキュメントをご覧ください。最も基本的なケースでの Intersection Observer v1 API は次のようになります。
const onIntersection = (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
console.log(entry);
}
}
};
const observer = new IntersectionObserver(onIntersection);
observer.observe(document.querySelector('#some-target'));
Intersection Observer v1 の課題は何ですか?
なお、Intersection Observer v1 は優れた機能ですが、完璧ではありません。API が不十分なコーナーケースもあります。詳しく見てみましょう。Intersection Observer v1 API は、要素がウィンドウのビューポートにスクロールされたタイミングを通知できますが、要素が他のページ コンテンツで覆われているかどうか(つまり、要素が遮蔽されているかどうか)、または要素の視覚的な表示が transform
、opacity
、filter
などの視覚効果によって変更され、実質的に要素が見えなくなっているかどうかは通知しません。
トップレベル ドキュメント内の要素の場合、この情報は JavaScript で DOM を分析し(DocumentOrShadowRoot.elementFromPoint()
など)、さらに詳細を調べることで確認できます。一方、問題の要素がサードパーティの iframe 内にある場合、同じ情報を取得することはできません。
実際の視認性が重要である理由
残念ながら、インターネットは悪意のある行為者を引き付ける場所です。たとえば、コンテンツ サイトでクリック課金広告を配信している不正なパブリッシャーは、ユーザーを騙して広告をクリックさせ、パブリッシャーの広告収入を増やす(少なくとも広告ネットワークが不正行為に気付くまでの短い期間)インセンティブを受け取る可能性があります。通常、このような広告は iframe で配信されます。パブリッシャーがそのような広告をクリックしてもらえるようにするには、CSS ルール iframe { opacity: 0; }
を適用して広告 iframe を完全に透明にし、ユーザーが実際にクリックしたくなるような魅力的なコンテンツ(かわいい猫の動画など)の上に iframe を重ねます。これは「クリックジャッキング」と呼ばれます。このようなクリックジャッキング攻撃の動作は、このデモの上部セクションで確認できます(猫の動画の「視聴」を試して「トリックモード」を有効にしてください)。iframe 内の広告は、(意図せず)クリックされたときに完全に透明だったとしても、正当なクリックが発生したと「認識」します。
Intersection Observer v2 ではどのように解決されるのですか?
Intersection Observer v2 では、人間が定義するように、ターゲット要素の実際の「可視性」をトラッキングするというコンセプトが導入されています。IntersectionObserver
コンストラクタでオプションを設定すると、交差する IntersectionObserverEntry
インスタンスに isVisible
という名前の新しいブール型フィールドが含まれます。isVisible
の true
値は、ターゲット要素が他のコンテンツによって完全に遮られていない、および画面上の表示を変更または歪める視覚効果が適用されていないことを、基盤となる実装から強く保証します。一方、値が false
の場合、実装でその保証を行うことはできません。
仕様の重要な詳細は、実装で偽陰性を報告することが許可されていることです(つまり、ターゲット要素が完全に可視で変更されていない場合でも、isVisible
を false
に設定します)。パフォーマンスなどの理由から、ブラウザは境界ボックスと直線ジオメトリの処理に限定されています。border-radius
などの変更でピクセル単位の正確な結果を得ようとはしません。
ただし、誤検出はいかなる状況でも許可されません(つまり、ターゲット要素が完全に表示されておらず、変更されていない場合に isVisible
を true
に設定することはできません)。
新しいコードの実際の例
IntersectionObserver
コンストラクタに、delay
と trackVisibility
の 2 つの構成プロパティが追加されました。delay
は、特定のターゲットに対するオブザーバーからの通知間の最小遅延(ミリ秒単位)を示す数値です。trackVisibility
は、オブザーバーがターゲットの公開設定の変更を追跡するかどうかを示すブール値です。
ここで重要なのは、trackVisibility
が true
の場合、delay
は少なくとも 100
にする必要があります(つまり、100 ミリ秒あたり 1 回以下の通知)。前述のとおり、可視性の計算は負荷が高いため、この要件はパフォーマンスの低下とバッテリーの消費を防ぐための予防措置です。担当デベロッパーは、遅延に許容できる最大値を使用します。
現在の仕様では、公開設定は次のように計算されます。
オブザーバーの
trackVisibility
属性がfalse
の場合、ターゲットは可視と見なされます。これは現在の v1 の動作に対応しています。ターゲットに 2D 変換または比例 2D アップスケーリング以外の有効な変換行列がある場合、ターゲットは非表示と見なされます。
ターゲットまたはそのブロックチェーン内の要素の効果的な不透明度が 1.0 以外の場合、ターゲットは非表示と見なされます。
ターゲットまたはそのブロックチェーン内の要素にフィルタが適用されている場合、ターゲットは非表示と見なされます。
実装で、ターゲットが他のページ コンテンツによって完全に遮られていない状態を保証できない場合、ターゲットは非表示と見なされます。
つまり、現在の実装では可視性を保証するためにかなり保守的な方法が取られています。たとえば、filter: grayscale(0.01%)
のようなほとんど目立たないグレースケール フィルタを適用したり、opacity: 0.99
でほとんど目立たない透明度を設定したりすると、要素はすべて非表示になります。
次のコードサンプルは、新しい API 機能を示しています。クリック トラッキング ロジックの動作は、デモの第 2 セクションで確認できます(まずは、子犬の動画の「視聴」を試してみてください)。必ず「トリックモード」を再度有効にして、すぐに不正なパブリッシャーに変身し、Intersection Observer v2 が不正な広告クリックの追跡を防ぐ仕組みを確認してください。今回は、Intersection Observer v2 が役に立ちます。🎉
<!DOCTYPE html>
<!-- This is the ad running in the iframe -->
<button id="callToActionButton">Buy now!</button>
// This is code running in the iframe.
// The iframe must be visible for at least 800ms prior to an input event
// for the input event to be considered valid.
const minimumVisibleDuration = 800;
// Keep track of when the button transitioned to a visible state.
let visibleSince = 0;
const button = document.querySelector('#callToActionButton');
button.addEventListener('click', (event) => {
if ((visibleSince > 0) &&
(performance.now() - visibleSince >= minimumVisibleDuration)) {
trackAdClick();
} else {
rejectAdClick();
}
});
const observer = new IntersectionObserver((changes) => {
for (const change of changes) {
// ⚠️ Feature detection
if (typeof change.isVisible === 'undefined') {
// The browser doesn't support Intersection Observer v2, falling back to v1 behavior.
change.isVisible = true;
}
if (change.isIntersecting && change.isVisible) {
visibleSince = change.time;
} else {
visibleSince = 0;
}
}
}, {
threshold: [1.0],
// 🆕 Track the actual visibility of the element
trackVisibility: true,
// 🆕 Set a minimum delay between notifications
delay: 100
}));
// Require that the entire iframe be visible.
observer.observe(document.querySelector('#ad'));
関連リンク
- インターセクション オブザーバーの仕様の最新の編集者ドラフト。
- Chrome プラットフォームのステータスの Intersection Observer v2。
- Intersection Observer v2 の Chromium バグ。
- 投稿の実装の意向を点滅させます。
謝辞
この記事の審査に協力してくれた Simeon Vincent、Yoav Weiss、Mathias Bynens の皆様、および Chrome への機能の実装と審査に協力してくれた Stefan Zager の皆様に感謝します。ヒーロー画像は、Unsplash の Sergey Semin によるものです。