Intersection Observer fait partie de ces API qui sont probablement appréciées de tous et qui sont utilisables dans tous les principaux navigateurs. Les développeurs ont utilisé cette API pour un large éventail de cas d'utilisation, y compris le chargement différé d'images et de vidéos, les notifications lorsque des éléments atteignent position: sticky, le déclenchement d'événements d'analyse et bien d'autres.
Dans sa forme la plus élémentaire, l'API Intersection Observer v1 se présente comme suit :
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'));
Problèmes de visibilité dans Intersection Observer v1
L'API Intersection Observer v1 vous permet de savoir quand un élément est affiché dans la fenêtre d'affichage. Toutefois, vous ne pouvez pas déterminer si cet élément est masqué par d'autres contenus de la page (on parle alors d'occlusion) ou s'il apparaît modifié par le CSS, par exemple avec transform, opacity ou filter, ce qui peut le rendre invisible.
Pour un élément du document de premier niveau, ces informations peuvent être déterminées en analysant le DOM avec JavaScript, par exemple avec DocumentOrShadowRoot.elementFromPoint().
En revanche, il est impossible d'obtenir les mêmes informations si l'élément en question se trouve dans un iFrame tiers.
Pourquoi la visibilité est-elle importante ?
Malheureusement, Internet est utilisé par des personnes malintentionnées. Par exemple, un éditeur malhonnête peut utiliser des annonces au paiement par clic sur un site Web. Ils peuvent inciter les utilisateurs à cliquer sur ces annonces pour gagner plus d'argent, au moins jusqu'à ce que le réseau publicitaire découvre leur stratagème. En général, ces annonces sont diffusées dans des iframes.
Pour tromper les utilisateurs, l'éditeur peut rendre les iframes d'annonces complètement transparents avec le code CSS suivant : iframe { opacity: 0; }. Ils peuvent ensuite placer ces iframes transparents sur des contenus attrayants, comme une vidéo de chat mignon, sur lesquels les utilisateurs veulent cliquer.
C'est ce qu'on appelle le clickjacking.
Vous pouvez voir une telle attaque par détournement de clic en action dans la section supérieure de notre démonstration. Essayez de "regarder" la vidéo du chat et d'activer le mode Trick. L'annonce dans l'iFrame enregistre les clics comme légitimes, même si vous avez cliqué dessus (involontairement) alors que l'iFrame était transparent.

Améliorations apportées à Intersection Observer v2
Intersection Observer v2 peut suivre la "visibilité" d'un élément telle qu'un humain la définirait. Si vous définissez une option dans le constructeur IntersectionObserver, les instances IntersectionObserverEntry résultantes incluent un nouveau champ booléen appelé isVisible. Lorsque isVisible est défini sur true, le navigateur s'assure que l'élément n'est pas recouvert par d'autres contenus et qu'il n'y a pas d'effets visuels qui masquent ou modifient son affichage. Si isVisible est défini sur false, le navigateur ne peut pas garantir cette fonctionnalité.
La spécification autorise les faux négatifs : isVisible peut être false même lorsque l'élément est réellement visible et inchangé. Pour améliorer les performances, les navigateurs utilisent des calculs plus simples, tels que des boîtes englobantes et des formes rectangulaires, et ne vérifient pas chaque pixel pour les détails complexes tels que border-radius.
Toutefois, les faux positifs ne sont autorisés en aucun cas. Cela signifie que isVisible ne sera pas true si l'élément n'est pas entièrement visible et non modifié.
Appliquer ces modifications
Le constructeur IntersectionObserver accepte désormais deux propriétés de configuration supplémentaires :
delayest un nombre qui indique le délai minimal en millisecondes entre les notifications de l'observateur, pour une cible donnée.trackVisibilityest une valeur booléenne indiquant si l'observateur suivra les modifications de la visibilité d'une cible.
Lorsque trackVisibility est défini sur true, delay doit être défini sur 100 ou une valeur supérieure (c'est-à-dire pas plus d'une notification toutes les 100 ms).
La visibilité étant coûteuse à calculer, il s'agit d'une précaution contre la dégradation des performances et la consommation de batterie. Les développeurs responsables doivent utiliser la valeur de délai la plus élevée tolérable.
La spécification calcule la visibilité. Comme pour la version 1, lorsque l'attribut trackVisibility de l'observateur est false, la cible est considérée comme visible.
Dans la version 2, la cible est considérée comme invisible si :
Elle possède une matrice de transformation efficace autre qu'une translation 2D ou une mise à l'échelle proportionnelle 2D.
La cible, ou tout élément de sa chaîne de blocs conteneur, a une opacité effective inférieure à 1.0.
Des filtres sont appliqués à la cible ou à un élément de sa chaîne de blocs conteneur.
Si l'implémentation ne peut pas garantir que la cible n'est pas complètement masquée par d'autres contenus de la page.
Cela signifie que les implémentations actuelles sont assez conservatrices pour garantir la visibilité. Par exemple, appliquer un filtre en niveaux de gris presque imperceptible (filter: grayscale(0.01%)) ou définir la transparence la plus faible (opacity: 0.99) rendrait l'élément invisible.
Voici un exemple de code qui illustre les nouvelles fonctionnalités de l'API. Vous pouvez voir sa logique de suivi des clics en action dans la deuxième section de la démo. Essayez de "regarder" la vidéo du chiot. Activez le mode Trick pour vous transformer en acteur malveillant et voir comment Intersection Observer v2 empêche le suivi des clics sur des annonces non légitimes. Intersection Observer v2 nous protège.

<!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 v2, fallback 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'));
Ressources supplémentaires
- Brouillon de travail de l'Intersection Observer.
- Intersection Observer v2 sur État de la plate-forme Chrome.
Remerciements
Merci à Simeon Vincent, Yoav Weiss et Mathias Bynens pour leur examen, ainsi qu'à Stefan Zager pour l'examen et l'implémentation de la fonctionnalité dans Chrome.