MDN will be in maintenance mode on Thursday September 21st, starting at 10 AM Pacific / 5 PM UTC, for about 1 hour.

Cette traduction est incomplète. Aidez à traduire cet article depuis l'anglais.

Cette fonction est expérimentale
Puisque cette fonction est toujours en développement dans certains navigateurs, veuillez consulter le tableau de compatibilité pour les préfixes à utiliser selon les navigateurs.
Il convient de noter qu'une fonctionnalité expérimentale peut voir sa syntaxe ou son comportement modifié dans le futur en fonction des évolutions de la spécification.

L'API Intersection Observer permet d'observer de manière asynchrone l'évolution de l'intersection d'un élément cible avec un élément ancêtre ou avec la zone d'affichage d'un document de niveau supérieur.

Historiquement, détecter la visibilité d'un élément, ou la visibilité relative de deux éléments l'un par rapport à l'autre, a été une tache difficile résolue de manière peu rigoureuse et pouvant nuire à la fluidité de la page consultée. Malheureusement, avec la maturation de la toile, ce genre d'information se révèle de plus en plus nécessaire. La donnée de l'intersection est requise pour de nombreuses raisons, telles que:

  • Le chargement paresseux d'images ou d'autres types de contenus au fur et à mesure que la page défile.
  • L'implémentation de "défilement infini", où de plus en plus de contenu est chargé tandis que l'utilisateur défile, afin qu'il n'ait pas à changer de page.
  • Signalement de la visibilité des publicités afin de calculer les revenus publicitaires.
  • La décision d'exécuter ou non une tâche ou une animation selon que l'utilisateur va en voir le résultat ou non.

De par le passé, l'implémentation de la détection d'intersection impliquait des gestionnaires d'évènements et des boucles appelant des méthodes telles que Element.getBoundingClientRect() afin de générer les informations nécessaires pour chaque élément concerné. Comme la totalité du code est exécuté dans le thread principal, même un seul d'entre eux peut causer des problèmes de performance. Si un site est rempli de ces tests, les choses peuvent vite devenir très moches.

Penons une page qui utilise un défilement infini. Mettons qu'elle utilise une librairie fournie par un marchand pour gérer les publicités placées périodiuqement le long de la page, qu'elle a des graphiques animés ici et là, et qu'elle utilise une librairie personalisée qui dessine les cases de notifications et ce genre de choses. Chacune de ces choses a ses propres procédures de détection d'intersection, toutes exécutées dans le thread principal. L'auteur du site ne le réalise peut-être même pas, puisqu'il utilise deux librairies dont il ne connaît pas forcément les détails de fonctionnement. Quand l'utilisateur navigue sur la page, ces procédures de détection d'intersection réagissent constamment pendant l'exécution du code de défilement, rendant l'expérience frustrante pour l'utilisateur vis-à-vis de son navigateur, du site Internet et de son ordinateur.

L'API Intersection Observer permet d'intégrer une fonction callback qui est exécutée quand un élément qu'on souhaite surveiller entre ou sort d'un autre élément (ou du viewport (fenêtre d'affichage)), ou quand la taille de leur intersection varie d'une quantité prédéterminée. Ainsi, les sites n'ont plus besoin de faire quoi que ce soit sur le thread principal pour surveiller ce genre d'intersection d'éléments, et le navigateur est libre la gestion des intersections comme bon lui semble.

Il y a une chose sur laquelle l'API Intersection Observer ne peut pas vous renseigner : le nombre de pixels qui intersectent, ou bien des quels il s'agit précisément; par contre elle permet d'ordonner des actions souvent utiles telles que "si ils s'intersectent de plus de N%, alors faire ceci".

Concepts et utilisation de l'observateur d'intersections

L'API Intersection Observer permet de mettre en place une fonction callback qui est appelée quand un élément, appelé la cible, intersecte ou bien la zone d'affichage, ou bien un élément prédéfini; dans le cadre de cette API, nous l'appelerons l'élément racine ou la racine. Typiquement, on voudra observer les variations de l'intersection par rapport à la zone d'affichage du document (ce qui est fait en passant l'argument null au moment de désigner l'élément racine). Que vous utilisiez la zone d'affichage ou un autre élément comme racine, l'API fonctionne de la même façon, en exécutant une fonction callback, fournie au préalable, quand la visibilité de l'élément cible change de telle sorte qu'il atteint la quantité voulue d'intersection avec l'élément racine.

Le degré d'intersection entre l'élément cible et sa racine est le ratio d'intersection. C'est une resprésentation du pourcentage de l'élément cible qui est visible, exprimée sous la forme d'un nombre compris entre 0.0 et 1.0.

Création d'un observateur d'intersection

Créez l'observateur d'intersection en appelant son constructeur et en lui passant la référence d'une fonction callback qui sera exécutée quand un palier est franchi dans un sens ou dans un autre:

var options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}

var observer = new IntersectionObserver(callback, options);

Un palier de 1.0 signifie que lorsque 100% de la cible est visible dans l'élément désigné par l'option root (l'élément racine), la fonction callback est invoquée.

Options de l'observateur d'intersection

L'objet options qui est passé dans le constructeur  IntersectionObserver() permet de contrôler les circonstances selon lesquelles la fonction callback de l'observateur est invoquée. Il possède les champs suivants:

root
L'élément qui est utilisé comme zone d'affichage au moment d'évaluer la visibilité de la cible. Il doit être un ancêtre de la cible. Si il n'est pas spécifié ou si il prends la valeur null, sa valeur par défaut est la zone d'affichage du navigateur.
rootMargin  
Marge autour de la racine. Peut prendre des valeurs similaires au CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). Si l'élément root a été spécifié, les valeurs peuvent être eprimées en pourcentages. Cet ensemble de valeur sert à agrandir ou réduire chaque coté du cadre délimitant l'élément racine avant d'évaluer les intersections. Par défaut, toutes les valeurs prennent la valeur zéro.
threshold
Soit un nombre, soit un tableau de nombre qui indique à quel pourcentage de la visibilité de la cible la fonction callback de la cible doit être exécuté. Si vous souhaitez seulement détecter quand est-ce que la visibilité franchihit la barre des 50%, vous pouvez entrer la valeur 0.5. Si vous voulez que le callback soit exécuté chaque fois que a visibilité varie de 25% de plus, il faudra spécifier le tableau [0, 0.25, 0.5, 0.75, 1]. La valeur par défaut est 0 (ce qui signifie que dés qu'un seul pixel est visible, la fonction callback sera exécutée). Une valeur de 1.0 signifie que le palier n'est considéré comme franchi qu'une fois que tous les pixels sont visibles.

Choisir un élément à observer

Une fois l'observateur créé, il faut lui donner un élément cible à observer:

var target = document.querySelector('#listItem');
observer.observe(target);

Lorsque la ce franchit un palier spécifié dans le IntersectionObserver, la tion callback appelée. Le callback reçoit une liste d'objets IntersectionObserverEntry  ainsi que l'observateur:

var callback = function(entries, observer) { 
  entries.forEach(entry => {
    // chaque élément de entries correspond à une variation
    // d'intersection pour un des éléments cible:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

Soyez attentif au fait que la fontion callback est exécutée dans le thread principal. Elle devrait être exécutée aussi rapidement que possible; si une opération prenant du temps a besoin d'être effectuée, utilisez Window.requestIdleCallback().

De plus, remarquez que si vous avez spécifi l'option root, la cible doit être un descendant de l'élément root.

Comment est calculée l'intersection

Toutes les régions envisagées par l'API Intersection Observer sont des rectangles; les éléments de forme irrégulière sont assimilées au plus petit rectangle qui contient l'élément en question tout entier. De même, si la partie visible d'un élément n'est pas rectangulaire, le rectangle d'intersection de l'élément sera le plus petit rectangle qui contient toute la partie visible de l'élément.

Il est utile de comprendre comment les différentes propriétés fournies par IntersectionObserverEntry décrivent une intersection.

La racine de l'intersection et la marge de la racine

Avant de pouvoir étudier l'intersection d'un élément avec un conteneur, nous devons savoir quel est ce conteneur. Ce conteneur est la racine de l'intersection, ou élément racine. Ce peut être soit un élément du document qui est un ancêtre de l'élément à observer, ou null si l'on souhaite utiliser la zone d'affichage du document comme conteneur.

Le rectangle utilisé pour délimiter la racine de l'intersection peut être ajusté en ajustant la marge de la racine, rootMargin, lors de la création de IntersectionObserver. La valeur de rootMargin définit le décalage ajouté à chaque coté du cadre délimitant la racine de l'intersection pour créer le cadre final de la racine de l'intersection (qui est accessible via IntersectionObserverEntry.rootBounds quand la fonction callback est exécutéé).

Thresholds

Rather than reporting every infinitesimal change in how much a target element is visible, the Intersection Observer API uses thresholds. When you create an observer, you can provide one or more numeric values representing percentages of the target element which are visible. Then, the API only reports changes to visibility which cross these thresholds.

For example, if you want to be informed every time a target's visibility passes backward or forward through each 25% mark, you would specify the array [0, 0.25, 0.5, 0.75, 1] as the list of thresholds when creating the observer. You can tell which direction the visibility changed in (that is, whether the element became more visible or less visible) by checking the value of the isIntersecting property on the IntersectionObserverEntry passed into the callback function at the time of the visibility change. If isIntersecting is true, the target element has become at least as visible as the threshold that was passed. If it's false, the target is no longer as visible as the given threshold.

To get a feeling for how thresholds work, try scrolling the box below around. Each colored box within it displays the percentage of itself that's visible in all four of its corners, so you can see these ratios change over time as you scroll the container. Each box has a different set of thresholds:

  • The first box has a threshold for each percentage point of visibility; that is, the IntersectionObserver.thresholds array is [0.00, 0.01, 0.02, ..., 0.99, 1.00].
  • The second box has a single threshold, at the 50% mark.
  • The third box has thresholds every 10% of visibility (0%, 10%, 20%, etc.).
  • The last box has thresholds each 25%.

Intersection change callbacks

When the amount of a target element which is visible within the root element crosses one of the visibility thresholds,

Interfaces

IntersectionObserver
The primary interface for the Intersection Observer API. Provides methods for creating and managing an observer which can watch any number of target elements for the same intersection configuration. Each observer can asynchronously observe changes in the intersection between one or more target elements and a shared ancestor element or with their top-level Document's viewport. The ancestor or viewport is referred to as the root.
IntersectionObserverEntry
Describes the intersection of a specific target elemethe observer's root as of a given time. Objects of this type can only be obtained in two ways: as an input to your IntersectionObserver callback, or by calling IntersectionObserver.takeRecords().

A simple example

This simple example causes a target element to change its color and transparency as it becomes more or less visible. At Timing element visibility with the Intersection Observer API, you can find a more extensive example showing how to time how long a set of elements (such as ads) are visible to the user and to react to that information by recording statistics or by updating elements..

HTML

The HTML for this example is very short, with a primary element which is the box that we'll be targeting (with the creative ID "box") and some contents within the box.

<div id="box">
  <div class="vertical">
    Welcome to <strong>The Box!</strong>
  </div>
</div>

CSS

The CSS isn't terribly important for the purposes of this example; it lays out the element and establishes that the background-color and border attributes can participate in CSS transitions, which we'll use to affect the changes to the element as it becomes more or less obscured.

#box {
  background-color: rgba(40, 40, 190, 255);
  border: 4px solid rgb(20, 20, 120);
  transition: background-color 1s, border 1s;
  width: 350px;
  height: 350px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.vertical {
  color: white;
  font: 32px "Arial";
}

.extra {
  width: 350px;
  height: 350px;
  margin-top: 10px;
  border: 4px solid rgb(20, 20, 120);
  text-align: center;
  padding: 20px;
}

JavaScript

Finally, let's take a look at the JavaScript code that uses the Intersection Observer API to make things happen.

Setting up

First, we need to prepare some variables and install the observer.

var numSteps = 20.0;

var boxElement;
var prevRatio = 0.0;
var increasingColor = "rgba(40, 40, 190, ratio)";
var decreasingColor = "rgba(190, 40, 40, ratio)";

// Set things up.

window.addEventListener("load", function(event) {
  boxElement = document.querySelector("#box");

  createObserver();
}, false);

The constants and variables we set up here are:

numSteps
A constant which indicates how many thresholds we want to have between a visibility ratio of 0.0 and 1.0.
prevRatio
This variable will be used to record what the visibility ratio was the last time a threshold was crossed; this will let us figure out whether the target element is becoming more or less visible.
increasingColor
A string defining a color we'll apply to the target element when the visibility ratio is increasing. The word "ratio" in this string will be replaced with the target's current visibility ratio, so that the element not only changes color but also becomes increasingly opaque as it becomes less obscured.
decreasingColor
Similarly, this is a string defining a color we'll apply when the visibility ratio is decreasing.

We call Window.addEventListener() to start listening for the load event; once the page has finished loading, we get a reference to the element with the ID "box" using querySelector(), then call the createObserver() method we'll create in a moment to handle building and installing the intersection observer.

Creating the intersection observer

The createObserver() method is called once page load is complete to handle actually creating the new IntersectionObserver and starting the process of observing the target element.

function createObserver() {
  var observer;

  var options = {
    root: null,
    rootMargin: "0px",
    threshold: buildThresholdList()
  };

  observer = new IntersectionObserver(handleIntersect, options);
  observer.observe(boxElement);
}

This begins by setting up an options object containing the settings for the observer. We want to watch for changes in visibility of the target element relative to the document's viewport, so root is null. We need no margin, so the margin offset, rootMargin, is specified as "0px". This causes the observer to watch for changes in the intersection between the target element's bounds and those of the viewport, without any added (or subtracted) space.

The list of visibility ratio thresholds, threshold, is constructed by the function buildThresholdList(). The threshold list is built programmatically in this example since there are a number of them and the number is intended to be adjustable.

Once options is ready, we create the new observer, calling the IntersectionObserver() constructor, specifying a function to be called when intersection crosses one of our thresholds, handleIntersect(), and our set of options. We then call observe() on the returned observer, passing into it the desired target element.

We could opt to monitor multiple elements for visibility intersection changes with respect to the viewport by calling observer.observe() for each of those elements, if we wanted to do so.

Building the array of threshold ratios

The buildThresholdList() function, which builds the list of thresholds, looks like this:

function buildThresholdList() {
  var thresholds = [];

  for (var i=1.0; i<=numSteps; i++) {
    var ratio = i/numSteps;
    thresholds.push(ratio);
  }

  thresholds.push(0);
  return thresholds;
}

This builds the array of thresholds—each of which is a ratio between 0.0 and 1.0, by pushing the value i/numSteps onto the thresholds array for each integer i between 1 and numSteps. It also pushes 0 to include that value. The result, given the default value of numSteps (20), is the following list of thresholds:

# Ratio # Ratio
1 0.05 11 0.55
2 0.1 12 0.6
3 0.15 13 0.65
4 0.2 14 0.7
5 0.25 15 0.75
6 0.3 16 0.8
7 0.35 17 0.85
8 0.4 18 0.9
9 0.45 19 0.95
10 0.5 20 1.0

We could, of course, hard-code the array of thresholds into our code, and often that's what you'll end up doing. But this example leaves room for adding configuration controls to adjust the granularity, for example.

Handling intersection changes

When the browser detects that the target element (in our case, the one with the ID "box") has been unveiled or obscured such that its visibility ratio crosses one of the thresholds in our list, it calls our handler function, handleIntersect():

function handleIntersect(entries, observer) {
  entries.forEach(function(entry) {
    if (entry.intersectionRatio > prevRatio) {
      entry.target.style.backgroundColor = increasingColor.replace("ratio", entry.intersectionRatio);
    } else {
      entry.target.style.backgroundColor = decreasingColor.replace("ratio", entry.intersectionRatio);
    }

    prevRatio = entry.intersectionRatio;
  });
}

For each IntersectionObserverEntry in the list entries, we look to see if the entry's intersectionRatio is going up; if it is, we set the target's background-color to the string in increasingColor (remember, it's "rgba(40, 40, 190, ratio)"), replaces the word "ratio" with the entry's intersectionRatio. The result: not only does the color get changed, but the transparency of the target element changes, too; as the intersection ratio goes down, the background color's alpha value goes down with it, resulting in an element that's more transparent.

Similarly, if the intersectionRatio is going up, we use the string decreasingColor and replace the word "ratio" in that with the intersectionRatio before setting the target element's background-color.

Finally, in order to track whether the intersection ratio is going up or down, we remember the current ratio in the variable prevRatio.

Result

Below is the resulting content. Scroll this page up and down and notice how the appearance of the box changes as you do so.

There's an even more extensive example at Timing element visibility with the Intersection Observer API.

Specifications

Specification Status Comment
Intersection Observer Brouillon de l'éditeur Initial definition.

Browser compatibility

Feature Chrome Edge Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
Basic support 51 15 55 (55)[1][2] Pas de support 38 WebKit bug 159475
Feature Android Webview Chrome for Android Firefox Mobile (Gecko) Firefox OS IE Mobile Opera Mobile Safari Mobile
Basic support 51 51 55.0 (55)[1][2] Pas de support Pas de support 38 WebKit bug 159475

[1] This feature has been implemented since Gecko 53.0 (Firefox 53.0 / Thunderbird 53.0 / SeaMonkey 2.50) behind the preference dom.IntersectionObserver.enabled, which was false by default. Enabled by default beginning in Firefox 55. See bug 1243846.

[2] Firefox doesn't currently take the clip-path of ancestor elements into account when computing the visibility of an element within its root. See bug 1319140 for the status of this issue.

See also

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : marc6310, baptistecolin
 Dernière mise à jour par : marc6310,