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(DocumentOrShadowRoot.elementFromPoint()
など)で DOM を分析し、さらに深く掘り下げることで確認できます。
一方、問題の要素がサードパーティの 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 によるものです。