W3docs

L'API JavaScript Resize Observer

Apprenez l'API JavaScript Resize Observer pour réagir aux changements de taille d'éléments individuels — composants adaptatifs, canvas, graphiques et champs de saisie auto-extensibles.

L'API ResizeObserver vous permet de réagir quand un élément spécifique change de taille, quelle qu'en soit la raison. Elle appartient à la même famille que le MutationObserver (qui surveille l'arbre DOM) et l'IntersectionObserver (qui surveille la visibilité) : des observers efficaces, pilotés par des callbacks, qui remplacent les boucles de polling maladroites.

Pourquoi ne pas utiliser l'événement resize ?

L'événement resize de la fenêtre ne se déclenche que lorsque la fenêtre du navigateur change de taille. Mais un élément de la page peut changer de taille pour de nombreuses autres raisons :

  • Une règle CSS change (une media query s'active, une classe est basculée).
  • Son contenu change (du texte est chargé, une image arrive, des lignes sont ajoutées à une liste).
  • Une mise en page Flexbox ou CSS Grid se recalcule parce qu'un élément frère a grandi ou rétréci.
  • Un conteneur parent est redimensionné par un séparateur déplaçable, une barre latérale ou un panneau.

Aucune de ces situations ne redimensionne nécessairement la fenêtre, donc window.addEventListener('resize', ...) ne se déclenche jamais. Vous pourriez utiliser setInterval et comparer getBoundingClientRect() toutes les quelques millisecondes, mais cela gaspille du CPU et accuse quand même un retard par rapport au changement réel.

ResizeObserver résout ce problème : il surveille un ou plusieurs éléments et ne vous rappelle que lorsque leur taille change réellement.

Info

ResizeObserver signale la taille du contenu d'un élément par défaut, et non sa position. Si vous devez savoir quand un élément se déplace ou entre dans le viewport, utilisez plutôt IntersectionObserver. Pour les mesures au niveau du viewport, consultez les tailles de fenêtre et le défilement.

Utilisation de base

Le schéma est le même que pour les autres observers : créez un observer avec un callback, puis indiquez-lui quel(s) élément(s) observer.

const box = document.querySelector('#box');

const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const { width, height } = entry.contentRect;
    console.log(`${entry.target.id} is now ${width} x ${height}`);
  }
});

ro.observe(box);

Le callback reçoit un array d'entrées — une par élément observé ayant changé dans ce lot — ainsi un seul observer peut surveiller de nombreux éléments à la fois.

Le contenu d'une entrée

Chaque entrée décrit un élément et sa nouvelle taille. Il existe deux façons de lire cette taille.

La façon simple est entry.contentRect, un DOMRectReadOnly avec width, height, top et left (les décalages sont relatifs à la boîte de rembourrage de l'élément) :

const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    console.log(entry.target);          // the element
    console.log(entry.contentRect.width);  // content-box width in px
    console.log(entry.contentRect.height); // content-box height in px
  }
});

La façon plus précise utilise les propriétés de taille de boîte, qui sont des arrays d'objets { inlineSize, blockSize } (un array car un élément peut être fragmenté sur plusieurs colonnes) :

  • entry.contentBoxSize — la boîte de contenu, hors rembourrage et bordure.
  • entry.borderBoxSize — la boîte de bordure, rembourrage et bordure inclus.
  • entry.devicePixelContentBoxSize — la boîte de contenu mesurée en pixels physiques, idéale pour un rendu net sur les écrans haute densité.
const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    // inlineSize ~ width, blockSize ~ height (for a horizontal writing mode)
    const { inlineSize, blockSize } = entry.borderBoxSize[0];
    console.log(`border box: ${inlineSize} x ${blockSize}`);
  }
});

Par défaut, l'observer réagit aux changements de la boîte de contenu. Pour observer la boîte de bordure à la place, passez un object d'options :

ro.observe(box, { box: 'border-box' });
Note

inlineSize et blockSize tiennent compte du mode d'écriture. Dans le mode par défaut gauche-à-droite, haut-en-bas, inlineSize correspond à la largeur et blockSize à la hauteur — mais dans un mode d'écriture vertical, ils s'inversent. Préférez-les à width/height codés en dur lorsque votre interface doit prendre en charge plusieurs directions d'écriture.

Méthodes

ResizeObserver expose trois méthodes :

  • observe(element, options) — commence à surveiller un élément. Appelez-la une fois par élément. L'option options.box peut être 'content-box' (par défaut), 'border-box' ou 'device-pixel-content-box'.
  • unobserve(element) — arrête de surveiller un seul élément.
  • disconnect() — arrête de surveiller tous les éléments en même temps.
ro.observe(el);        // start
ro.unobserve(el);      // stop watching this one
ro.disconnect();       // stop watching everything

Cas d'usage 1 — Composants adaptatifs (« Container Queries en JS »)

Une media query réagit au viewport. Mais une carte réutilisable peut être large dans la colonne principale et étroite dans une barre latérale pour un même viewport. Avec ResizeObserver, vous pouvez adapter un composant à sa propre largeur :

const card = document.querySelector('.card');

const ro = new ResizeObserver(([entry]) => {
  const width = entry.contentRect.width;
  // Toggle a layout class based on the element's own width.
  card.classList.toggle('card--compact', width < 400);
});

ro.observe(card);
.card { display: flex; gap: 1rem; }
.card--compact { flex-direction: column; }

La carte s'empile maintenant verticalement dès qu'elle est plus étroite que 400 px, indépendamment de la taille de la fenêtre — pratique dans les tableaux de bord, les volets divisés et les widgets intégrables.

Astuce

Le CSS moderne peut le faire nativement avec les container queries (@container). Si vous n'avez besoin que de re-styliser en fonction de la taille d'un conteneur, préférez les container queries CSS — elles sont déclaratives et s'exécutent hors du thread principal. Recourez à ResizeObserver lorsque vous devez exécuter du JavaScript en réponse au changement de taille (recalculer des valeurs, redessiner un canvas, recalculer un graphique).

Cas d'usage 2 — Redimensionnement d'un canvas ou d'un graphique

Un <canvas> a deux tailles : sa taille d'affichage CSS et la taille de son tampon de dessin (canvas.width/canvas.height). Si elles divergent, le bitmap est étiré et apparaît flou. ResizeObserver maintient la synchronisation du tampon avec la taille affichée pour que les dessins restent nets :

const canvas = document.querySelector('#chart');
const ctx = canvas.getContext('2d');

const ro = new ResizeObserver(([entry]) => {
  // Use device-pixel size for sharp rendering on high-DPI screens.
  const size = entry.devicePixelContentBoxSize?.[0];
  const width = size ? size.inlineSize : entry.contentRect.width;
  const height = size ? size.blockSize : entry.contentRect.height;

  canvas.width = width;
  canvas.height = height;
  redraw(ctx, width, height); // your drawing / charting code
});

ro.observe(canvas, { box: 'device-pixel-content-box' });

La même idée s'applique aux bibliothèques de graphiques : observez le conteneur du graphique et appelez la méthode resize() de la bibliothèque quand le conteneur change, au lieu de vous connecter uniquement à window.resize.

Cas d'usage 3 — Textarea auto-extensible

Comme le callback se déclenche chaque fois que la taille mesurée de l'élément change, vous pouvez maintenir des éléments dépendants synchronisés. Un exemple classique est de répercuter la hauteur d'un textarea sur un élément frère, ou de réagir à une croissance pilotée par le contenu :

const textarea = document.querySelector('#message');
const counter = document.querySelector('#height-readout');

const ro = new ResizeObserver(([entry]) => {
  const h = Math.round(entry.contentRect.height);
  counter.textContent = `${h}px tall`;
});

ro.observe(textarea);

Chaque fois que l'utilisateur fait glisser la poignée de redimensionnement du textarea — ou que votre code modifie sa hauteur à mesure que l'utilisateur tape — l'affichage se met à jour automatiquement, sans événement resize ni polling.

L'avertissement « ResizeObserver loop »

Vous pouvez rencontrer ce message dans la console :

ResizeObserver loop completed with undelivered notifications.

(Certains navigateurs l'expriment ainsi : « ResizeObserver loop limit exceeded. ») Il survient lorsque votre callback modifie la taille de l'élément observé, ce qui déclenche à nouveau l'observer, qui modifie à nouveau la taille — une boucle de rétroaction.

// Anti-pattern: this can cause the loop warning.
const ro = new ResizeObserver(([entry]) => {
  // Resizing the observed element from inside its own callback. Bad.
  entry.target.style.height = entry.contentRect.width + 'px';
});
Avertissement

Ne redimensionnez pas l'élément observé de façon synchrone à l'intérieur de son propre callback. Si un redimensionnement piloté par la taille est inévitable, brisez le cycle : n'écrivez que lorsque la valeur a réellement changé (une garde), ou différez l'écriture avec requestAnimationFrame. L'avertissement est généralement sans danger et se corrige de lui-même, mais une véritable boucle infinie nuira aux performances.

Nettoyage

Un observer conserve une référence à chaque élément qu'il surveille, ce qui peut empêcher cet élément d'être collecté par le ramasse-miettes. Arrêtez toujours l'observation lorsque vous avez terminé — par exemple, quand un composant est démonté ou qu'une vue est détruite :

function mountWidget(el) {
  const ro = new ResizeObserver(handleResize);
  ro.observe(el);

  // Return a cleanup function.
  return () => ro.disconnect();
}

Oublier cela est une source courante de fuites mémoire dans les applications monopage. Pour en savoir plus sur l'optimisation du travail lié à la taille et à la mise en page, consultez l'optimisation des performances DOM.

ResizeObserver est pris en charge par tous les navigateurs modernes, vous pouvez donc vous y fier sans polyfill dans tout navigateur evergreen actuel.

Testez vos connaissances

Pratique
Pourquoi préférer ResizeObserver à l'événement 'resize' de la fenêtre pour un composant ?
Pourquoi préférer ResizeObserver à l'événement 'resize' de la fenêtre pour un composant ?
Pratique
Que vous fournit chaque entrée de ResizeObserver ?
Que vous fournit chaque entrée de ResizeObserver ?
Pratique
Qu'est-ce qui déclenche généralement l'avertissement 'ResizeObserver loop' ?
Qu'est-ce qui déclenche généralement l'avertissement 'ResizeObserver loop' ?
Was this page helpful?