Intersection Observer v2 permet non seulement d'observer les intersections en tant que telles, mais aussi de détecter si l'élément qui se croise était visible au moment de l'intersection.
Intersection Observer v1 est l'une de ces API universellement appréciées. Maintenant que Safari est également compatible avec elle, elle est enfin utilisable dans tous les principaux navigateurs. Pour rafraîchir vos connaissances sur l'API, nous vous recommandons de regarder la micro-astuce surpuissante de Surma sur IntersectionObserver v1, intégrée ci-dessous.
Vous pouvez également lire l'article détaillé de Surma.
Intersection Observer v1 a été utilisé pour un large éventail de cas d'utilisation, comme le chargement différé d'images et de vidéos, l'envoi de notifications lorsque des éléments atteignent position: sticky
, le déclenchement d'événements d'analyse, et bien plus encore.
Pour en savoir plus, consultez la documentation Intersection Observer sur MDN. Pour rappel, voici à quoi ressemble l'API Intersection Observer v1 dans le cas le plus simple :
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'));
Quels sont les défis liés à Intersection Observer v1 ?
Pour être clair, la version 1 d'Intersection Observer est excellente, mais elle n'est pas parfaite. Il existe des cas particuliers où l'API est insuffisante. Voyons cela de plus près.
L'API Intersection Observer v1 peut vous indiquer quand un élément est défilé dans le viewport de la fenêtre, mais elle ne vous indique pas si l'élément est recouvert par un autre contenu de la page (c'est-à-dire lorsqu'il est masqué) ni si son affichage visuel a été modifié par des effets visuels tels que transform
, opacity
, filter
, etc., qui peuvent effectivement le rendre invisible.
Pour un élément du document de premier niveau, ces informations peuvent être déterminées en analysant le DOM via JavaScript, par exemple via DocumentOrShadowRoot.elementFromPoint()
, puis en approfondissant l'analyse.
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é réelle est-elle si importante ?
Malheureusement, Internet attire des acteurs mal intentionnés.
Par exemple, un éditeur douteux qui diffuse des annonces payantes par clic sur un site de contenu peut être incité à inciter les utilisateurs à cliquer sur ses annonces afin d'augmenter ses revenus publicitaires (au moins pendant une courte période, jusqu'à ce que le réseau publicitaire le découvre).
En règle générale, ces annonces sont diffusées dans des iFrames.
Si l'éditeur souhaite inciter les utilisateurs à cliquer sur de telles annonces, il peut rendre les iFrames d'annonces complètement transparentes en appliquant une règle CSS iframe { opacity: 0; }
et en superposant les iFrames sur un élément attrayant, comme une vidéo de chat mignon sur laquelle les utilisateurs voudront cliquer.
C'est ce qu'on appelle le détournement de clic.
Vous pouvez voir une telle attaque de hameçonnage par clic en action dans la section supérieure de cette démo (essayez de "regarder" la vidéo sur les chats et activez le "mode piège").
Vous remarquerez que l'annonce dans l'iFrame "pense" avoir reçu des clics légitimes, même si elle était complètement transparente lorsque vous avez cliqué dessus (en prétendant que c'était involontaire).
Comment Intersection Observer v2 résout-il ce problème ?
Intersection Observer v2 introduit le concept de suivi de la "visibilité" réelle d'un élément cible tel qu'un être humain le définirait.
En définissant une option dans le constructeur IntersectionObserver
, les instances IntersectionObserverEntry
qui se croisent contiennent alors un nouveau champ booléen nommé isVisible
.
Une valeur true
pour isVisible
est une garantie forte de l'implémentation sous-jacente que l'élément cible n'est pas du tout masqué par d'autres contenus et qu'aucun effet visuel n'est appliqué pour modifier ou déformer son affichage à l'écran.
En revanche, une valeur false
signifie que l'implémentation ne peut pas garantir cela.
Un détail important de la spécification est que l'implémentation est autorisée à signaler des faux négatifs (c'est-à-dire à définir isVisible
sur false
même lorsque l'élément cible est complètement visible et non modifié).
Pour des raisons de performances ou d'autres, les navigateurs se limitent à utiliser des rectangles de délimitation et une géométrie rectiligne. Ils ne tentent pas d'obtenir des résultats précis au pixel près pour les modifications telles que border-radius
.
Toutefois, les faux positifs ne sont pas autorisés en aucun cas (c'est-à-dire que isVisible
ne doit pas être défini sur true
lorsque l'élément cible n'est pas complètement visible et non modifié).
À quoi ressemble le nouveau code en pratique ?
Le constructeur IntersectionObserver
utilise désormais deux propriétés de configuration supplémentaires : delay
et trackVisibility
.
delay
est un nombre indiquant le délai minimal en millisecondes entre les notifications de l'observateur pour une cible donnée.
trackVisibility
est une valeur booléenne indiquant si l'observateur suivra les modifications de la visibilité d'une cible.
Il est important de noter ici que lorsque trackVisibility
est défini sur true
, delay
doit être au moins 100
(c'est-à-dire, pas plus d'une notification toutes les 100 ms).
Comme indiqué précédemment, la visibilité est coûteuse à calculer. Il s'agit d'une précaution contre la dégradation des performances et l'utilisation de la batterie. Le développeur responsable utilisera la valeur la plus élevée tolérable pour le délai.
Selon les spécifications actuelles, la visibilité est calculée comme suit:
Si l'attribut
trackVisibility
de l'observateur estfalse
, la cible est considérée comme visible. Cela correspond au comportement actuel de la version 1.Si la cible dispose d'une matrice de transformation efficace autre qu'une traduction en 2D ou une augmentation de taille proportionnelle en 2D, elle est considérée comme invisible.
Si la cible ou un élément de la chaîne de blocs qui la contient présente une opacité effective autre que 1,0, la cible est considérée comme invisible.
Si des filtres sont appliqués à la cible ou à un élément de la chaîne de blocs qui la contient, la cible est considérée comme invisible.
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, la cible est considérée comme invisible.
Cela signifie que les implémentations actuelles sont assez conservatrices et qu'elles garantissent la visibilité.
Par exemple, appliquer un filtre en niveaux de gris presque imperceptible comme filter: grayscale(0.01%)
ou définir une transparence presque invisible avec opacity: 0.99
rendrait l'élément invisible.
Vous trouverez ci-dessous un court extrait de code qui illustre les nouvelles fonctionnalités de l'API. Vous pouvez voir son fonctionnement dans la deuxième section de la démo (mais pour l'instant, essayez de "regarder" la vidéo sur les chiots). Veillez à réactiver le mode "trick" pour vous transformer immédiatement en éditeur douteux et voir comment Intersection Observer v2 empêche le suivi des clics sur les annonces non légitimes. Cette fois, Intersection Observer v2 est là pour nous aider. 🎉
<!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'));
Liens associés
- Dernière version provisoire de l'spécification Intersection Observer.
- Intersection Observer v2 sur État de la plate-forme Chrome.
- Bug Chromium dans Intersection Observer v2.
- Blink Intent to Implement posting (Intent d'implémentation de la publication).
Remerciements
Merci à Simeon Vincent, Yoav Weiss et Mathias Bynens pour avoir relu cet article, ainsi qu'à Stefan Zager pour avoir examiné et implémenté la fonctionnalité dans Chrome. Image héros de Sergey Semin sur Unsplash.