ResizeObserver: il s'apparente à document.onresize pour les éléments.

ResizeObserver vous indique quand la taille d'un élément change.

Avant ResizeObserver, vous deviez associer un écouteur à l'événement resize du document pour être averti de toute modification des dimensions de la fenêtre d'affichage. Dans le gestionnaire d'événements, vous devrez ensuite déterminer quels éléments ont été affectés par ce changement et appeler une routine spécifique pour réagir de manière appropriée. Si vous aviez besoin des nouvelles dimensions d'un élément après un redimensionnement, vous deviez appeler getBoundingClientRect() ou getComputedStyle(), ce qui peut entraîner un thrashing de mise en page si vous ne prenez pas soin de regrouper toutes vos lectures et toutes vos écritures.

Cela ne couvrait même pas les cas où les éléments changeaient de taille sans que la fenêtre principale ait été redimensionnée. Par exemple, l'ajout d'enfants, la définition du style display d'un élément sur none ou des actions similaires peuvent modifier la taille d'un élément, de ses frères et sœurs ou de ses ancêtres.

C'est pourquoi ResizeObserver est une primitive utile. Il réagit aux changements de taille de l'un des éléments observés, quelle que soit la cause du changement. Il permet également d'accéder à la nouvelle taille des éléments observés.

Browser Support

  • Chrome: 64.
  • Edge: 79.
  • Firefox: 69.
  • Safari: 13.1.

Source

API

Toutes les API avec le suffixe Observer que nous avons mentionnées ci-dessus partagent une conception d'API simple. ResizeObserver ne fait pas exception. Vous créez un objet ResizeObserver et transmettez un rappel au constructeur. Le rappel reçoit un tableau d'objets ResizeObserverEntry (une entrée par élément observé) qui contient les nouvelles dimensions de l'élément.

var ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;

    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);

Quelques détails

Quelles sont les données collectées ?

En général, ResizeObserverEntry indique la zone de contenu d'un élément via une propriété appelée contentRect, qui renvoie un objet DOMRectReadOnly. La zone de contenu est celle dans laquelle le contenu peut être placé. Il s'agit du cadre de délimitation moins la marge intérieure.

Diagramme du modèle de boîte CSS.

Il est important de noter que, bien que ResizeObserver signale à la fois les dimensions de contentRect et le remplissage, il ne surveille que contentRect. Ne confondez pas contentRect avec le cadre de délimitation de l'élément. Le cadre de délimitation, tel qu'indiqué par getBoundingClientRect(), est celui qui contient l'intégralité de l'élément et de ses descendants. Les SVG font exception à la règle. Dans ce cas, ResizeObserver indique les dimensions du cadre de délimitation.

Depuis Chrome 84, ResizeObserverEntry comporte trois nouvelles propriétés pour fournir des informations plus détaillées. Chacune de ces propriétés renvoie un objet ResizeObserverSize contenant une propriété blockSize et une propriété inlineSize. Ces informations concernent l'élément observé au moment où le rappel est invoqué.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

Tous ces éléments renvoient des tableaux en lecture seule, car nous espérons qu'ils pourront à l'avenir prendre en charge les éléments comportant plusieurs fragments, qui se produisent dans les scénarios à plusieurs colonnes. Pour l'instant, ces tableaux ne contiendront qu'un seul élément.

La compatibilité de ces propriétés avec les plates-formes est limitée, mais Firefox prend déjà en charge les deux premières.

Quand le problème a-t-il été signalé ?

La spécification prévoit que ResizeObserver doit traiter tous les événements de redimensionnement avant la peinture et après la mise en page. Cela fait du rappel d'un ResizeObserver l'endroit idéal pour modifier la mise en page de votre page. Étant donné que le traitement ResizeObserver se produit entre la mise en page et la peinture, cela n'invalidera que la mise en page, et non la peinture.

Gotcha

Vous vous demandez peut-être ce qui se passe si vous modifiez la taille d'un élément observé à l'intérieur du rappel de ResizeObserver. La réponse est la suivante : vous déclencherez immédiatement un autre appel au rappel. Heureusement, ResizeObserver dispose d'un mécanisme permettant d'éviter les boucles de rappel infinies et les dépendances cycliques. Les modifications ne seront traitées dans le même frame que si l'élément redimensionné est plus profond dans l'arborescence DOM que l'élément le moins profond traité dans le rappel précédent. Sinon, ils seront reportés à la frame suivante.

Application

ResizeObserver vous permet, entre autres, d'implémenter des requêtes média par élément. En observant les éléments, vous pouvez définir de manière impérative vos points d'arrêt de conception et modifier les styles d'un élément. Dans l'exemple suivant, la deuxième boîte modifie le rayon de sa bordure en fonction de sa largeur.

const ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    entry.target.style.borderRadius =
        Math.max(0, 250 - entry.contentRect.width) + 'px';
  }
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));

Un autre exemple intéressant à examiner est une fenêtre de chat. Le problème qui se pose dans une mise en page de conversation typique de haut en bas est le positionnement du défilement. Pour éviter de dérouter l'utilisateur, il est utile que la fenêtre reste en bas de la conversation, où les messages les plus récents s'affichent. De plus, tout changement de mise en page (par exemple, un téléphone qui passe du mode Paysage au mode Portrait ou inversement) devrait produire le même résultat.

ResizeObserver vous permet d'écrire un seul code qui gère les deux scénarios. Le redimensionnement de la fenêtre est un événement qu'un ResizeObserver peut capturer par définition, mais l'appel de appendChild() redimensionne également cet élément (sauf si overflow: hidden est défini), car il doit faire de la place pour les nouveaux éléments. Dans cette optique, il ne faut que quelques lignes pour obtenir l'effet souhaité :

const ro = new ResizeObserver(entries => {
  document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
});

// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);

// Observe the timeline to process new messages
ro.observe(timeline);

Plutôt sympa, non ?

À partir de là, je pourrais ajouter du code pour gérer le cas où l'utilisateur a fait défiler l'écran manuellement vers le haut et souhaite que le défilement reste sur ce message lorsqu'un nouveau message arrive.

Un autre cas d'utilisation concerne tout type d'élément personnalisé qui effectue sa propre mise en page. Jusqu'à ResizeObserver, il n'existait aucun moyen fiable de recevoir une notification lorsque ses dimensions changeaient afin que ses enfants puissent être réorganisés.

Effets sur l'interaction to Next Paint (INP)

L'Interaction to Next Paint (INP) est une métrique qui mesure la réactivité globale d'une page aux interactions des utilisateurs. Si l'INP d'une page se situe dans le seuil "Bon" (c'est-à-dire 200 millisecondes ou moins), on peut dire que la page est fiable et réactive aux interactions de l'utilisateur.

Bien que le temps nécessaire à l'exécution des rappels d'événements en réponse à une interaction utilisateur puisse contribuer de manière significative à la latence totale d'une interaction, ce n'est pas le seul aspect de l'INP à prendre en compte. L'INP tient également compte du temps nécessaire pour que le next paint de l'interaction se produise. Il s'agit du temps nécessaire pour que le rendu requis pour mettre à jour l'interface utilisateur en réponse à une interaction soit terminé.

En ce qui concerne ResizeObserver, cela est important, car le rappel qu'une instance ResizerObserver exécute se produit juste avant le travail de rendu. C'est normal, car le travail effectué dans le rappel doit être pris en compte, car le résultat de ce travail nécessitera très probablement une modification de l'interface utilisateur.

Veillez à effectuer le moins de rendu possible dans un rappel ResizeObserver, car un rendu excessif peut entraîner des situations où le navigateur est retardé dans l'exécution de tâches importantes. Par exemple, si une interaction comporte un rappel qui entraîne l'exécution d'un rappel ResizeObserver, veillez à effectuer les opérations suivantes pour offrir la meilleure expérience possible :

  • Assurez-vous que vos sélecteurs CSS sont aussi simples que possible afin d'éviter un travail excessif de recalcul des styles. Les recalculs de style se produisent juste avant la mise en page, et les sélecteurs CSS complexes peuvent retarder les opérations de mise en page.
  • Évitez d'effectuer des tâches dans votre rappel ResizeObserver qui peuvent déclencher des reflows forcés.
  • Le temps nécessaire pour mettre à jour la mise en page d'une page augmente généralement avec le nombre d'éléments DOM sur une page. Bien que cela soit vrai, que les pages utilisent ou non ResizeObserver, le travail effectué dans un rappel ResizeObserver peut devenir important à mesure que la complexité structurelle d'une page augmente.

Conclusion

ResizeObserver est disponible dans tous les principaux navigateurs et constitue un moyen efficace de surveiller le redimensionnement des éléments au niveau de l'élément. Faites simplement attention à ne pas trop retarder le rendu avec cette API puissante.