L’API Intersection Observer, en Javascript, permet de suivre la visibilité d’un élément au sein d’une page ou d’un conteneur.
Problématique
Quand on parcourt une page, on fait défiler son contenu (généralement, verticalement).
Sur la droite de notre écran, une barre de défilement (scrollbar) permet de suivre notre progression dans la page.
Dans une page web, si on voulait suivre la visibilité de certains éléments, on pourrait écouter l’événément scroll pour réagir au défilement de la page :
window.addEventListener("scroll", () => {
const scrollPosition = window.scrollY;
// effectuer des actions en fonction de la position
// donc en fonction de ce qui est visible
});
Cette approche peut tout à fait fonctionner, mais quel est le problème ?
- L’écouteur d’événement se lancera sans arrêt, au moindre scroll, ce qui pourrait surcharger le thread principal
- Pour déterminer si un élément de la page est visible, on récupèrera sa position (avec
getBoundingClientRect()par exemple) et on la comparera au défilement dans la page, donc on doit effectuer le calcul manuellement - Si on veut déterminer la visibilité d’un élément au sein d’un conteneur scrollable qui se trouverait dans la page, alors il faut également gérer les calculs manuellement, en fonction de la position de l’élément parent
Intersection Observer
L’API Intersection Observer vient gérer ces problématiques naturellement.
À partir d’un élément racine, on va observer plusieurs éléments au sein d’une zone d’intersection définie.
Quand n’importe lequel des éléments observés entrera ou sortira de la zone d’intersection, alors on pourra exécuter une fonction.
Élément racine
À la création d’une instance de IntersectionObserver, on peut lui passer des options.
Parmi ces options, on peut lui indiquer un élément racine : root. Cet élément, s’il n’est pas indiqué, sera simplement toute la page. Sinon, ça peut être n’importe quel conteneur scrollable dans lequel on veut surveiller la visibilité de certains éléments.
const observer = new IntersectionObserver(
callback, // fonction de rappel
{
// élément racine
root: document.getElementById("container"),
},
);
Fonction de rappel
À la création d’une instance IntersectionObserver, on enregistre une fonction de rappel.
Cette fonction s’exécutera dans deux situations :
- À la création de l’instance
- Quand un élément observé est considéré comme entrant ou sortant de la zone d’intersection
Elle prend deux paramètres : les éléments concernés, et l’instance de IntersectionObserver :
const observer = new IntersectionObserver(
(entries, observer) => {},
// options...
);
Dans entries, on a un tableau des éléments concernés, chacun du type IntersectionObserverEntry. On peut alors boucler dessus et en tirer des informations très utiles, comme entre autres :
isIntersecting, la plus pratique : est-ce que l’élément est dans la zone d’intersection ?intersectionRatio, peut être utile : quel pourcentage de l’élément est en intersection avec l’élément racine ?
Observation des éléments
Pour observer un élément, rien de plus simple. Une fois l’instance de IntersectionObserver créée, on appelle sa méthode observe en lui passant l’élément à observer :
observer.observe(element);
À partir de là, dès que l’élément traverse la zone d’intersection, alors la fonction de rappel est déclenchée.
La zone d’intersection
Par défaut, la zone d’intersection couvre l’ensemble de l’élément racine. Dès qu’un élément observé touche cette zone, alors il sera considéré comme étant visible.
const observer = new IntersectionObserver(callback);
Dans l’exemple ci-dessus, dès que les éléments rentrent dans la zone, ils sont considérés comme visibles. Logiquement, on a donc l’impression que tous les éléments sont tout le temps visibles.
rootMargin
Il est possible de d’agrandir ou de réduire la zone d’intersection en passant une option à l’instance de IntersectionObserver : rootMargin.
L’option rootMargin accepte la même syntaxe que la propriété CSS margin : top right bottom left. On peut donc lui indiquer de une à quatre valeurs, en pixels, pourcentage, ou bien les deux à la fois.
Regardons tout de suite un exemple avec rootMargin à -100px, qui réduit la zone d’intersection de 100 pixels sur tous les côtés :
const observer = new IntersectionObserver(callback, {
rootMargin: "-100px",
});
Testez vous-même avec le slider ci-dessous. On veut réduire la zone d’intersection donc on appliquera une valeur négative :
threshold
L’option threshold permet d’ajouter un seuil à partir duquel l’élément sera considéré comme visible. La valeur peut varier :
- Un nombre entre
0et1correspond à un pourcentage entre0%et100%. Par exemple si on passe0.8, alors l’élément est considéré comme visible quand au moins 80% de son contenu est visible
Exemple avec 0.8 :
const observer = new IntersectionObserver(callback, {
threshold: 0.8,
});
- Un tableau de valeurs entre
0et1, indiquant à l’instance deIntersectionObservertoutes les valeurs auxquelles il faudra déclencher la fonction de rappel
Exemple avec un appel de la fonction tous les 10%, qui affiche le pourcentage de visibilité de l’élément (intersectionRatio) :
const observer = new IntersectionObserver(callback, {
threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
});
Output :Gérer une table des matières
Un exemple d’utilisation de l’API Intersection Observer est la gestion d’une table des matières.
Au fil de la lecture de l’article, la section courante se met en surbrillance.
C’est exactement ce que fait Astro Starlight : quand un titre de section entre en intersection avec une zone définie, alors l’élément de table des matières correspondant se met en surbrillance.
Astro Starlight contient un web component. C’est dans ce composant qu’on retrouvera la création de l’instance IntersectionObserver par exemple.
On y trouvera également le calcul de la zone d’intersection avec l’option rootMargin : on prend la hauteur de la navbar, on y ajoute la hauteur de la table des matières sur mobile, c’est le début de la zone d’intersection. 53 pixels plus tard, c’est le bas de la zone. Quand un titre traversera cette zone, alors l’élément de table des matières correspondant changera d’apparence.
Astro Starlight définit en réalité deux web components : un pour mobile (affiché en haut de l’écran), et un pour les écrans plus larges (affiché à droite).