IntersectionObserver

Observe si un élément est visible dans la fenêtre ou dans un conteneur défilant, et appelle une fonction quand cela change.

Renvoie
Un nouvel objet IntersectionObserver.
Sur cette page

Exemple

#
JavaScript
const feed = document.querySelector('#feed')
const lessons = feed.querySelectorAll('p')

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    const state = entry.isIntersecting ? 'visible' : 'cachée'
    console.log(`${entry.target.textContent}: ${state}`)
  }
}, { root: feed })

lessons.forEach((lesson) => observer.observe(lesson))
console.log(`J’observe ${lessons.length} leçons`)
Console
J’observe 5 leçons
Variables: visible
Conditions: visible
Boucles: visible
Fonctions: cachée
Tableaux: cachée

La première entrée vient du code synchrone, et l’état des leçons arrive plus tard, dans la fonction de rappel. « Boucles » n’est que partiellement visible, mais avec le threshold: 0 par défaut, un seul pixel suffit. Faites défiler la liste dans l’aperçu, et la console affichera d’autres changements.

Définition et utilisation

#

IntersectionObserver vous informe quand un élément entre dans la zone visible ou en sort. Plutôt que de vérifier la position des éléments à chaque défilement de la page, vous créez un observateur avec une fonction de rappel, et le navigateur l’appelle lui-même au bon moment. C’est la base du chargement différé, des listes infinies, des animations déclenchées au défilement et du comptage des affichages.

La fonction de rappel reçoit un tableau d’objets IntersectionObserverEntry. Les champs les plus importants sont target (l’élément observé), isIntersecting (si l’élément est visible) et intersectionRatio (quelle fraction de l’élément est visible, de 0 à 1). Le premier appel a lieu juste après observe(), donc vous connaissez immédiatement l’état de départ.

Vous passez les options dans le deuxième argument du constructeur. root est l’élément par rapport auquel la visibilité est calculée (la fenêtre du navigateur par défaut). rootMargin agrandit ou réduit cette zone, par exemple "200px" permet de commencer le chargement avant que l’élément apparaisse à l’écran. threshold détermine à partir de quelle fraction visible appeler la fonction, par exemple 0.5 ou [0, 0.5, 1].

La méthode observe() ajoute un élément à l’observation, unobserve() le retire, et disconnect() met fin à toute l’observation. La fonction de rappel s’exécute de façon asynchrone, donc ses entrées dans la console apparaissent après le code synchrone.

Syntaxe

#
Syntaxe
new IntersectionObserver(callback)
new IntersectionObserver(callback, options)

observer.observe(element)
observer.unobserve(element)
observer.disconnect()

Paramètres

#
  • callback

    function (entries, observer)

    La fonction appelée lors d’un changement de visibilité. Elle reçoit un tableau d’entrées et l’observateur lui-même.
  • options

    objet, optionnel

    root (un élément, ou null pour la fenêtre), rootMargin (une marge écrite comme en CSS, par ex. "0px 0px 200px 0px") et threshold (un nombre ou un tableau de nombres de 0 à 1).

Plus d’exemples

#
Quelle fraction de l’élément est visible
JavaScript
const viewport = document.querySelector('#viewport')

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    const percent = Math.round(entry.intersectionRatio * 100)
    console.log(`${entry.target.textContent}: visible à ${percent} %`)
  }
}, { root: viewport, threshold: [0, 0.25, 0.5, 0.75, 1] })

viewport.querySelectorAll('.card').forEach((card) => observer.observe(card))
Console
HTML: visible à 100 %
CSS: visible à 25 %

La seconde carte dépasse du conteneur, donc on n’en voit qu’un quart. Faites défiler le conteneur dans l’aperçu, et l’observateur se déclenchera chaque fois que la partie visible franchit l’un des seuils de threshold.

Liste de tâches infinie
JavaScript
const list = document.querySelector('#list')
const sentinel = document.querySelector('#sentinel')
let page = 0

const observer = new IntersectionObserver(([entry]) => {
  if (!entry.isIntersecting) return
  page += 1

  for (let i = 1; i <= 5; i++) {
    const item = document.createElement('li')
    item.textContent = `Tâche ${(page - 1) * 5 + i}`
    sentinel.before(item)
  }
  console.log(`Page ${page} chargée`)

  if (page === 3) {
    observer.disconnect()
    sentinel.textContent = 'Ce sont toutes les tâches'
  }
}, { root: list })

observer.observe(sentinel)
Console
Page 1 chargée

Un élément sentinelle se trouve à la fin de la liste. Quand il devient visible, on ajoute d’autres tâches juste avant lui. Faites défiler la liste jusqu’en bas dans l’aperçu pour charger les pages suivantes. Après la troisième, l’observateur se déconnecte.

Chargement différé des images
JavaScript
const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue
    const img = entry.target
    img.src = img.dataset.src
    observer.unobserve(img)
  }
}, { rootMargin: '200px' })

document.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img))

Sans l’option root, la visibilité est calculée par rapport à la fenêtre du navigateur. L’image ne reçoit sa véritable adresse qu’une fois à 200 px de l’écran, puis elle cesse d’être observée. Dans les cas simples, l’attribut HTML loading="lazy" suffit à obtenir le même résultat.

Compatibilité des navigateurs

#

Largement disponible · depuis 2019

Fonctionne dans tous les navigateurs modernes, y compris sur mobile. Vous pouvez l’utiliser sans crainte.

  • Chrome
  • Edge
  • Firefox
  • Safari

Bonnes pratiques

#
  • Après une action ponctuelle, comme le chargement d’une image, appelez observer.unobserve(element). L’observateur ne continuera pas à suivre l’élément pour rien.
  • Un seul observateur peut suivre plusieurs éléments. Ne créez pas un observateur séparé pour chaque élément d’une liste.
  • Pour le simple chargement différé d’images, l’attribut loading="lazy" en HTML suffit souvent. Utilisez un observateur quand vous avez besoin de votre propre logique.

Entrées associées

#

Vous voyez une erreur ou il manque un exemple ? Écrivez-nous.