Un guide complet sur l'API JavaScript ResizeObserver pour créer des composants véritablement réactifs, conscients de leur taille, et gérer des mises en page dynamiques avec haute performance.
API ResizeObserver : Le secret du web moderne pour un suivi sans effort de la taille des éléments et des mises en page réactives
Dans le monde du développement web moderne, nous construisons des applications avec des composants. Nous pensons en termes de blocs d'interface utilisateur autonomes et réutilisables — cartes, tableaux de bord, widgets et barres latérales. Pourtant, pendant des années, notre principal outil pour le design réactif, les media queries CSS, a été fondamentalement déconnecté de cette réalité basée sur les composants. Les media queries ne se soucient que d'une chose : la taille du viewport global. Cette limitation a contraint les développeurs, conduisant à des calculs complexes, des mises en page fragiles et des hacks JavaScript inefficaces.
Et si un composant pouvait être conscient de sa propre taille ? Et s'il pouvait adapter sa mise en page non pas parce que la fenêtre du navigateur a été redimensionnée, mais parce que le conteneur dans lequel il se trouve a été comprimé par un élément voisin ? C'est le problème que l'API ResizeObserver résout avec élégance. Elle fournit un mécanisme de navigateur performant, fiable et natif pour réagir aux changements de taille de n'importe quel élément du DOM, inaugurant une ère de véritable réactivité au niveau de l'élément.
Ce guide complet explorera l'API ResizeObserver depuis ses fondements. Nous verrons ce que c'est, pourquoi c'est une amélioration monumentale par rapport aux méthodes précédentes, et comment l'utiliser à travers des exemples pratiques et concrets. À la fin, vous serez équipé pour construire des mises en page plus robustes, modulaires et dynamiques que jamais.
L'ancienne méthode : Les limites de la réactivité basée sur le viewport
Pour apprécier pleinement la puissance de ResizeObserver, nous devons d'abord comprendre les défis qu'il surmonte. Depuis plus d'une décennie, notre boîte à outils réactive a été dominée par deux approches : les media queries CSS et l'écoute d'événements en JavaScript.
Le carcan des media queries CSS
Les media queries CSS sont une pierre angulaire du design web réactif. Elles nous permettent d'appliquer différents styles en fonction des caractéristiques de l'appareil, le plus souvent la largeur et la hauteur du viewport.
Une media query typique ressemble à ceci :
/* Si la fenêtre du navigateur fait 600px de large ou moins, le fond du body devient lightblue */
@media screen and (max-width: 600px) {
body {
background-color: lightblue;
}
}
Cela fonctionne à merveille pour les ajustements de mise en page de haut niveau. Mais considérez un composant de carte `UserInfo` réutilisable. Vous pourriez vouloir que cette carte affiche un avatar à côté du nom de l'utilisateur dans une mise en page large, mais empile l'avatar au-dessus du nom dans une mise en page étroite. Si cette carte est placée dans une zone de contenu principale large, elle devrait utiliser la mise en page large. Si cette même carte est placée dans une barre latérale étroite, elle devrait automatiquement adopter la mise en page étroite, quelle que soit la largeur totale du viewport.
Avec les media queries, c'est impossible. La carte n'a aucune connaissance de son propre contexte. Son style est entièrement dicté par le viewport global. Cela oblige les développeurs à créer des classes de variantes comme .user-card--narrow
et à les appliquer manuellement, brisant la nature autonome du composant.
Les pièges de performance des astuces JavaScript
L'étape suivante naturelle pour les développeurs confrontés à ce problème était de se tourner vers JavaScript. L'approche la plus courante était d'écouter l'événement `resize` de `window`.
window.addEventListener('resize', () => {
// Pour chaque composant sur la page qui doit être réactif...
// Obtenir sa largeur actuelle
// Vérifier s'il dépasse un seuil
// Appliquer une classe ou changer les styles
});
Cette approche présente plusieurs défauts critiques :
- Cauchemar de performance : L'événement `resize` peut se déclencher des dizaines, voire des centaines de fois lors d'une seule opération de redimensionnement par glisser-déposer. Si votre fonction de gestion effectue des calculs complexes ou des manipulations du DOM pour plusieurs éléments, vous pouvez facilement causer de graves problèmes de performance, des saccades (jank) et du "layout thrashing".
- Toujours dépendant du viewport : L'événement est lié à l'objet `window`, pas à l'élément lui-même. Votre composant ne change toujours que lorsque la fenêtre entière est redimensionnée, et non lorsque son conteneur parent change pour d'autres raisons (par exemple, l'ajout d'un élément frère, l'expansion d'un accordéon, etc.).
- Sondage (polling) inefficace : Pour détecter les changements de taille non causés par un redimensionnement de la fenêtre, les développeurs ont eu recours à des boucles `setInterval` ou `requestAnimationFrame` pour vérifier périodiquement les dimensions d'un élément. C'est très inefficace, consommant constamment des cycles CPU et drainant la batterie sur les appareils mobiles, même lorsque rien ne change.
Ces méthodes étaient des solutions de contournement, pas des solutions. Le web avait besoin d'une meilleure méthode — une API efficace et centrée sur les éléments pour observer les changements de taille. Et c'est exactement ce que fournit ResizeObserver.
Voici ResizeObserver : Une solution moderne et performante
Qu'est-ce que l'API ResizeObserver ?
L'API ResizeObserver est une interface de navigateur qui vous permet d'être notifié lorsque la taille de la boîte de contenu ou de la bordure d'un élément change. Elle offre un moyen asynchrone et performant de surveiller les changements de taille des éléments sans les inconvénients du sondage manuel ou de l'événement `window.resize`.
Pensez-y comme un `IntersectionObserver` pour les dimensions. Au lieu de vous dire quand un élément entre dans la vue lors du défilement, il vous dit quand la taille de sa boîte a été modifiée. Cela peut se produire pour de nombreuses raisons :
- La fenêtre du navigateur est redimensionnée.
- Du contenu est ajouté ou retiré de l'élément (par ex., un retour à la ligne du texte).
- Les propriétés CSS de l'élément comme `width`, `height`, `padding` ou `font-size` sont modifiées.
- La taille du parent d'un élément change, le faisant rétrécir ou s'agrandir.
Avantages clés par rapport aux méthodes traditionnelles
ResizeObserver n'est pas seulement une amélioration mineure ; c'est un changement de paradigme pour la gestion de la mise en page au niveau des composants.
- Très performant : L'API est optimisée par le navigateur. Elle ne déclenche pas un rappel pour chaque changement de pixel. Au lieu de cela, elle regroupe les notifications et les livre efficacement dans le cycle de rendu du navigateur (généralement juste avant le "paint"), empêchant le "layout thrashing" qui tourmente les gestionnaires de `window.resize`.
- Spécifique à l'élément : C'est son super-pouvoir. Vous observez un élément spécifique, et le rappel ne se déclenche que lorsque la taille de cet élément change. Cela découple la logique de votre composant du viewport global, permettant une véritable modularité et le concept d'"Element Queries".
- Simple et déclaratif : L'API est remarquablement facile à utiliser. Vous créez un observateur, lui dites quels éléments surveiller, et fournissez une seule fonction de rappel pour gérer toutes les notifications.
- Précis et complet : L'observateur fournit des informations détaillées sur la nouvelle taille, y compris la boîte de contenu, la boîte de bordure et le remplissage (padding), vous donnant un contrôle précis sur votre logique de mise en page.
Comment utiliser ResizeObserver : Un guide pratique
L'utilisation de l'API implique trois étapes simples : créer un observateur, observer un ou plusieurs éléments cibles, et définir la logique de rappel. Voyons cela en détail.
La syntaxe de base
Le cœur de l'API est le constructeur `ResizeObserver` et ses méthodes d'instance.
// 1. Sélectionnez l'élément que vous souhaitez surveiller
const myElement = document.querySelector('.my-component');
// 2. Définissez la fonction de rappel qui s'exécutera lorsqu'un changement de taille est détecté
const observerCallback = (entries) => {
for (let entry of entries) {
// L'objet 'entry' contient des informations sur la nouvelle taille de l'élément observé
console.log('La taille de l\'élément a changé !');
console.log('Élément cible :', entry.target);
console.log('Nouveau rect de contenu :', entry.contentRect);
console.log('Nouvelle taille de la border box :', entry.borderBoxSize[0]);
}
};
// 3. Créez une nouvelle instance de ResizeObserver en lui passant la fonction de rappel
const observer = new ResizeObserver(observerCallback);
// 4. Commencez à observer l'élément cible
observer.observe(myElement);
// Pour arrêter d'observer un élément spécifique plus tard :
// observer.unobserve(myElement);
// Pour arrêter d'observer tous les éléments liés à cet observateur :
// observer.disconnect();
Comprendre la fonction de rappel et ses entrées
La fonction de rappel que vous fournissez est au cœur de votre logique. Elle reçoit un tableau d'objets `ResizeObserverEntry`. C'est un tableau car l'observateur peut livrer des notifications pour plusieurs éléments observés en un seul lot.
Chaque objet `entry` contient des informations précieuses :
entry.target
: Une référence à l'élément du DOM qui a changé de taille.entry.contentRect
: Un objet `DOMRectReadOnly` fournissant les dimensions de la boîte de contenu de l'élément (width, height, x, y, top, right, bottom, left). C'est une propriété plus ancienne et il est généralement recommandé d'utiliser les nouvelles propriétés de taille de boîte ci-dessous.entry.borderBoxSize
: Un tableau contenant un objet avec `inlineSize` (largeur) et `blockSize` (hauteur) de la boîte de bordure de l'élément. C'est le moyen le plus fiable et pérenne d'obtenir la taille totale d'un élément. C'est un tableau pour supporter des cas d'utilisation futurs comme les mises en page multi-colonnes où un élément pourrait être divisé en plusieurs fragments. Pour l'instant, vous pouvez presque toujours utiliser en toute sécurité le premier élément : `entry.borderBoxSize[0]`.entry.contentBoxSize
: Similaire à `borderBoxSize`, mais fournit les dimensions de la boîte de contenu (à l'intérieur du padding).entry.devicePixelContentBoxSize
: Fournit la taille de la boîte de contenu en pixels de l'appareil.
Une bonne pratique essentielle : Préférez `borderBoxSize` et `contentBoxSize` à `contentRect`. Ils sont plus robustes, s'alignent sur les propriétés logiques CSS modernes (`inlineSize` pour la largeur, `blockSize` pour la hauteur), et représentent la voie à suivre pour l'API.
Cas d'utilisation concrets et exemples
La théorie, c'est bien, mais ResizeObserver brille vraiment quand on le voit en action. Explorons quelques scénarios courants où il offre une solution propre et puissante.
1. Mises en page de composants dynamiques (L'exemple de la "carte")
Résolvons le problème de la carte `UserInfo` que nous avons évoqué plus tôt. Nous voulons que la carte passe d'une disposition horizontale à une disposition verticale lorsqu'elle devient trop étroite.
HTML :
<div class="card-container">
<div class="user-card">
<img src="avatar.jpg" alt="User Avatar" class="user-card-avatar">
<div class="user-card-info">
<h3>Jane Doe</h3>
<p>Senior Frontend Developer</p>
</div>
</div>
</div>
CSS :
.user-card {
display: flex;
align-items: center;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
transition: all 0.3s ease;
}
/* État de la mise en page verticale */
.user-card.is-narrow {
flex-direction: column;
text-align: center;
}
.user-card-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin-right: 1rem;
}
.user-card.is-narrow .user-card-avatar {
margin-right: 0;
margin-bottom: 1rem;
}
JavaScript avec ResizeObserver :
const card = document.querySelector('.user-card');
const cardObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const { inlineSize } = entry.borderBoxSize[0];
// Si la largeur de la carte est inférieure à 350px, ajoutez la classe 'is-narrow'
if (inlineSize < 350) {
entry.target.classList.add('is-narrow');
} else {
entry.target.classList.remove('is-narrow');
}
}
});
cardObserver.observe(card);
Maintenant, peu importe où cette carte est placée. Si vous la mettez dans un conteneur large, elle sera horizontale. Si vous réduisez la taille du conteneur, le `ResizeObserver` détectera le changement et appliquera automatiquement la classe `.is-narrow`, réorganisant le contenu. C'est la véritable encapsulation de composant.
2. Visualisations de données et graphiques réactifs
Les bibliothèques de visualisation de données comme D3.js, Chart.js ou ECharts ont souvent besoin de se redessiner lorsque leur élément conteneur change de taille. C'est un cas d'utilisation parfait pour `ResizeObserver`.
const chartContainer = document.getElementById('chart-container');
// Supposons que 'myChart' est une instance d'un graphique d'une bibliothèque
// avec une méthode 'redraw(width, height)'.
const myChart = createMyChart(chartContainer);
const chartObserver = new ResizeObserver(entries => {
const entry = entries[0];
const { inlineSize, blockSize } = entry.contentBoxSize[0];
// Le dé-rebondissement (debouncing) est souvent une bonne idée ici pour éviter de redessiner trop fréquemment,
// bien que ResizeObserver regroupe déjà les appels.
requestAnimationFrame(() => {
myChart.redraw(inlineSize, blockSize);
});
});
chartObserver.observe(chartContainer);
Ce code garantit que, quelle que soit la manière dont `chart-container` est redimensionné — via un panneau divisible d'un tableau de bord, une barre latérale repliable ou un redimensionnement de la fenêtre — le graphique sera toujours re-rendu pour s'adapter parfaitement à ses limites, sans aucun écouteur `window.onresize` qui tue les performances.
3. Typographie adaptative
Parfois, vous voulez qu'un titre remplisse une quantité spécifique d'espace horizontal, avec sa taille de police s'adaptant à la largeur du conteneur. Bien que CSS dispose maintenant de `clamp()` et d'unités de requête de conteneur pour cela, `ResizeObserver` vous donne un contrôle JavaScript précis.
const adaptiveHeading = document.querySelector('.adaptive-heading');
const headingObserver = new ResizeObserver(entries => {
const entry = entries[0];
const containerWidth = entry.borderBoxSize[0].inlineSize;
// Une formule simple pour calculer la taille de la police.
// Vous pouvez la rendre aussi complexe que nécessaire.
const newFontSize = Math.max(16, containerWidth / 10);
entry.target.style.fontSize = `${newFontSize}px`;
});
headingObserver.observe(adaptiveHeading);
4. Gérer la troncature et les liens "Lire la suite"
Un modèle d'interface utilisateur courant consiste à afficher un extrait de texte et un bouton "Lire la suite" uniquement si le texte intégral dépasse de son conteneur. Cela dépend à la fois de la taille du conteneur et de la longueur du contenu.
const textBox = document.querySelector('.truncatable-text');
const textContent = textBox.querySelector('p');
const truncationObserver = new ResizeObserver(entries => {
const entry = entries[0];
const target = entry.target;
// Vérifiez si la hauteur de défilement (scroll height) est supérieure à la hauteur visible (client height)
const isOverflowing = target.scrollHeight > target.clientHeight;
target.classList.toggle('is-overflowing', isOverflowing);
});
truncationObserver.observe(textContent);
Votre CSS peut alors utiliser la classe `.is-overflowing` pour afficher un dégradé et le bouton "Lire la suite". L'observateur s'assure que cette logique s'exécute automatiquement chaque fois que la taille du conteneur change, affichant ou masquant correctement le bouton.
Considérations sur la performance et meilleures pratiques
Bien que `ResizeObserver` soit très performant par conception, il y a quelques bonnes pratiques et pièges potentiels à connaître.
Éviter les boucles infinies
L'erreur la plus courante est de modifier une propriété de l'élément observé à l'intérieur du rappel, ce qui provoque à son tour un autre redimensionnement. Par exemple, si vous ajoutez du padding à l'élément, sa taille changera, ce qui déclenchera à nouveau le rappel, qui ajoutera plus de padding, et ainsi de suite.
// DANGER : Boucle infinie !
const badObserver = new ResizeObserver(entries => {
const el = entries[0].target;
// Changer le padding redimensionne l'élément, ce qui déclenche à nouveau l'observateur.
el.style.paddingLeft = parseInt(el.style.paddingLeft || 0) + 1 + 'px';
});
Les navigateurs sont intelligents et détecteront cela. Après quelques rappels rapides dans la même frame, ils s'arrêteront et lanceront une erreur : `ResizeObserver loop limit exceeded`.
Comment l'éviter :
- Vérifiez avant de changer : Avant d'effectuer un changement, vérifiez s'il est réellement nécessaire. Par exemple, dans notre exemple de carte, nous ajoutons/retirons seulement une classe, nous ne changeons pas continuellement une propriété de largeur.
- Modifiez un enfant : Si possible, placez l'observateur sur un conteneur parent et effectuez les modifications de taille sur un élément enfant. Cela brise la boucle car l'élément observé lui-même n'est pas modifié.
- Utilisez `requestAnimationFrame` : Dans certains cas complexes, envelopper votre modification du DOM dans `requestAnimationFrame` peut différer le changement à la frame suivante, brisant la boucle.
Quand utiliser `unobserve()` et `disconnect()`
Tout comme avec `addEventListener`, il est crucial de nettoyer vos observateurs pour éviter les fuites de mémoire, en particulier dans les applications monopages (SPA) construites avec des frameworks comme React, Vue ou Angular.
Lorsqu'un composant est démonté ou détruit, vous devez appeler `observer.unobserve(element)` ou `observer.disconnect()` si l'observateur n'est plus du tout nécessaire. Dans React, cela se fait généralement dans la fonction de nettoyage d'un hook `useEffect`. Dans Angular, vous utiliseriez le hook de cycle de vie `ngOnDestroy`.
Support des navigateurs
À ce jour, `ResizeObserver` est pris en charge par tous les principaux navigateurs modernes, y compris Chrome, Firefox, Safari et Edge. Le support est excellent pour un public mondial. Pour les projets nécessitant un support pour de très anciens navigateurs comme Internet Explorer 11, un polyfill peut être utilisé, mais pour la plupart des nouveaux projets, vous pouvez utiliser l'API nativement en toute confiance.
ResizeObserver vs. le futur : Les requêtes de conteneur CSS (Container Queries)
Il est impossible de discuter de `ResizeObserver` sans mentionner son homologue déclaratif : les requêtes de conteneur CSS (CSS Container Queries). Les requêtes de conteneur (`@container`) vous permettent d'écrire des règles CSS qui s'appliquent à un élément en fonction de la taille de son conteneur parent, et non du viewport.
Pour notre exemple de carte, le CSS pourrait ressembler à ceci avec les requêtes de conteneur :
.card-container {
container-type: inline-size;
}
/* La carte elle-même n'est pas le conteneur, c'est son parent qui l'est */
.user-card {
display: flex;
/* ... autres styles ... */
}
@container (max-width: 349px) {
.user-card {
flex-direction: column;
}
}
Cela produit le même résultat visuel que notre exemple avec `ResizeObserver`, mais entièrement en CSS. Alors, est-ce que cela rend `ResizeObserver` obsolète ? Absolument pas.
Pensez-y comme des outils complémentaires pour des tâches différentes :
- Utilisez les requêtes de conteneur CSS lorsque vous devez changer le style d'un élément en fonction de la taille de son conteneur. Ce devrait être votre choix par défaut pour des changements purement présentationnels.
- Utilisez ResizeObserver lorsque vous devez exécuter une logique JavaScript en réponse à un changement de taille. C'est essentiel pour les tâches que CSS ne peut pas gérer, telles que :
- Déclencher le re-rendu d'une bibliothèque de graphiques.
- Effectuer des manipulations complexes du DOM.
- Calculer les positions d'éléments pour un moteur de mise en page personnalisé.
- Interagir avec d'autres API en fonction de la taille d'un élément.
Ils résolvent le même problème de fond sous des angles différents. `ResizeObserver` est l'API impérative et programmatique, tandis que les requêtes de conteneur sont la solution déclarative et native en CSS.
Conclusion : Adoptez une conception consciente des éléments
L'API `ResizeObserver` est un élément fondamental pour le web moderne, axé sur les composants. Elle nous libère des contraintes du viewport et nous donne le pouvoir de construire des composants véritablement modulaires et conscients d'eux-mêmes, capables de s'adapter à n'importe quel environnement dans lequel ils sont placés. En fournissant un moyen performant et fiable de surveiller les dimensions des éléments, elle élimine le besoin de hacks JavaScript fragiles et inefficaces qui ont tourmenté le développement frontend pendant des années.
Que vous construisiez un tableau de bord de données complexe, un système de design flexible, ou simplement un unique widget réutilisable, `ResizeObserver` vous donne le contrôle précis dont vous avez besoin pour gérer les mises en page dynamiques avec confiance et efficacité. C'est un outil puissant qui, combiné avec les techniques de mise en page modernes et les futures requêtes de conteneur CSS, permet une approche plus résiliente, maintenable et sophistiquée du design réactif. Il est temps d'arrêter de ne penser qu'à la page et de commencer à construire des composants qui comprennent leur propre espace.