</> HTML5Advent
ENFRESDEITPT

// apis · Web Platform Advent #10

L'API IntersectionObserver : lazy-loading et effets au scroll

Utilisez IntersectionObserver pour charger les images en différé, révéler des éléments au scroll et créer un défilement infini — threshold et rootMargin expliqués, avec des exemples exécutables.

Vue de dessus d'un bureau de designer avec des croquis de maquettes d'interface, des post-it, un smartphone et des marqueurs de couleur

L'API IntersectionObserver vous indique quand un élément entre ou sort de la fenêtre d'affichage — sans avoir à brancher un écouteur scroll et à recalculer des positions à chaque image. Le navigateur effectue la surveillance de façon asynchrone et hors du thread principal, ce qui reste fluide même avec des centaines de cibles. Tous les navigateurs actuels la prennent en charge.

Pourquoi ne pas simplement écouter le scroll ?

Un gestionnaire scroll se déclenche en permanence et vous oblige à appeler getBoundingClientRect() pour savoir où se trouvent les éléments — ce qui lit le layout et peut provoquer des saccades. IntersectionObserver inverse le modèle : vous enregistrez les éléments qui vous intéressent, et le navigateur ne vous prévient que lorsque leur visibilité change réellement.

Le schéma de base

Créez un observateur avec une fonction de rappel, puis appelez observe() sur un ou plusieurs éléments. Le rappel reçoit un tableau d'objets entry ; la propriété clé est entry.isIntersecting :

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      console.log('Visible maintenant :', entry.target);
    }
  }
});

document.querySelectorAll('.watch').forEach((el) => observer.observe(el));

Charger les images en différé (lazy-loading)

Un usage classique : ne charger une image que lorsqu'elle est sur le point d'apparaître. Stockez l'URL réelle dans data-src, injectez-la quand l'élément intersecte, puis cessez de l'observer :

const lazyImages = document.querySelectorAll('img[data-src]');

const imgObserver = new IntersectionObserver((entries, observer) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue;
    const img = entry.target;
    img.src = img.dataset.src;
    observer.unobserve(img); // charger une fois, puis arrêter
  }
}, { rootMargin: '200px' });

lazyImages.forEach((img) => imgObserver.observe(img));

À noter : le lazy-loading natif via <img loading="lazy"> couvre désormais la plupart des cas. Utilisez IntersectionObserver quand vous avez besoin de logique sur mesure — placeholders, fondus, ou chargement de ressources autres que des images.

Un smartphone affichant l'écran de connexion Facebook à côté de lettres de Scrabble formant les mots SOCIAL MEDIA
Les fils à défilement infini comme les réseaux sociaux sont un cas d'école d'IntersectionObserver : on surveille une sentinelle proche du bas et on charge plus de contenu lorsqu'elle apparaît.

Révéler des éléments au scroll

Ajoutez une classe CSS lorsqu'un élément devient visible pour la première fois, afin qu'il puisse s'animer. L'option threshold décide quelle part de l'élément doit être visible avant le déclenchement :

const revealObserver = new IntersectionObserver((entries, observer) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      entry.target.classList.add('is-visible');
      observer.unobserve(entry.target);
    }
  }
}, { threshold: 0.2 }); // déclenche quand 20% est à l'écran

document.querySelectorAll('.reveal').forEach((el) => revealObserver.observe(el));

Défilement infini

Placez un élément sentinelle vide après votre liste. Quand il entre dans la fenêtre, récupérez et ajoutez la page suivante, puis continuez à observer pour la suivante :

const sentinel = document.querySelector('#sentinel');
let page = 1;

const loader = new IntersectionObserver(async (entries) => {
  if (!entries[0].isIntersecting) return;
  const items = await fetchPage(page++);
  appendItems(items);
});

loader.observe(sentinel);

Les options : threshold et rootMargin

Deux options déterminent le moment du déclenchement :

  • threshold — un nombre (ou un tableau) de 0 à 1. 0 déclenche dès qu'un pixel est visible ; 1 uniquement quand tout l'élément l'est. Un tableau comme [0, 0.5, 1] déclenche à chaque palier, pratique pour les effets de progression au scroll.
  • rootMargin — une marge à la CSS qui agrandit ou réduit la zone de test. '200px' déclenche 200px avant que l'élément n'atteigne la fenêtre — idéal pour précharger.
  • root — l'élément servant de fenêtre. Par défaut, la fenêtre du navigateur ; indiquez un conteneur défilable pour observer à l'intérieur de celui-ci.

Nettoyage

Cessez de surveiller un seul élément avec observer.unobserve(el), ou démantelez tout avec observer.disconnect() — important dans les applications monopage lorsqu'une vue est démontée, pour éviter les fuites.

Aide-mémoire

ObjectifOption / appel clé
Détecter la visibilitéentry.isIntersecting
Précharger en avancerootMargin: '200px'
Attendre X% visiblethreshold: 0.5
Observer dans un conteneurroot: containerEl
Arrêter pour un élémentobserver.unobserve(el)
Tout démantelerobserver.disconnect()

IntersectionObserver remplace toute une catégorie de calculs de scroll fragiles par une petite API déclarative. Images en différé, révélations au scroll et listes infinies se ramènent au même schéma : observer, réagir à isIntersecting, puis cesser d'observer quand c'est fini.