Si c'est très facile en JavaScript de détecter le changement de taille de la fenêtre (à ne pas confondre avec la taille du viewport) car un simple onresize sur l'élément body suffit, c'est moins évident de détecter le changement de taille d'un élément car on a aucun événement directement accessible. Heureusement on peut utiliser la classe ResizeObserver !

Comment on fait en JavaScript / TypeScript ?

Le pattern est simple : on crée une instance de ResizeObserver avec la/les action·s à effectuer si un changement se produit, on lui demande d'écouter les changements de taille de notre/nos élément·s et c'est tout !

En termes de code :


const resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {
   console.log(entries[0].borderBoxSize) 
   console.log(entries[0].contentBoxSize) 
   console.log(entries[0].contentRect) 
   console.log(entries[0].devicePixelContentBoxSize) 
   console.log(entries[0].target);
});

resizeObserver.observe(document.querySelector('#myDiv'));

Comme on peut le voir la structure est très simple, et il suffit de peu de code pour avoir un ResizeObserver fonctionnel. Attention quand même l'exemple est un peu naïf et il faudrait aussi valider la taille du tableau entries qui peut être vide (si on observe aucun élément), on peut aussi observer plusieurs éléments auquel cas il faudra parcourir entries et pas juste piocher le premier élément. On notera aussi que chacune des propriétés de ResizeObserverEntry est un tableau de ResizeObserverSize sauf contentRect (c'est un DOMRectReadOnly) et target (c'est un Element) donc il faudra aussi parcourir la liste des dimensions pour avoir l'info qu'on souhaite.

Comment je fais en React ?

Si vous n'aurez aucun mal à copier-collerr l'exemple au-dessus dans une application web écrite avec n'importe quel framework, ça vous semblera moins trivial en React.

En effet avec React on est face un système de boucle de rendu qui risque de créer des fuites mémoires, du Virtual DOM et un système qui n'aime pas trop qu'on accède aux éléments du DOM…

Je vous montre un hook, prêt à copier-coller, qui permet de gérer toute cette complexité !


function useResizeObserver<T extends Element>(
  callback: (entries: ResizeObserverEntry[]) => void,
  refs: React.RefObject<T>[]
) {
  return useEffect(() => {
    const resizeObserver = new ResizeObserver(callback);
    refs
      .map((ref) => ref.current)
      .filter(isDefined)
      .forEach((elt) => resizeObserver.observe(elt));
    return () => resizeObserver.disconnect();
  }, [callback, refs]);
}

function isDefined<T>(x: T | null | undefined): x is T {
  return x != null;
}

Et son utilisation :


function App() {
  const ref = useRef<HTMLTextAreaElement>(null);
  const [eltWidth, setEltWidth] = useState(0);
  const [eltHeight, setEltHeight] = useState(0);
  useResizeObserver(
    (entries) => {
      setEltWidth(entries[0]?.contentRect.width);
      setEltHeight(entries[0]?.contentRect.height);
    },
    [ref]
  );

  return (
    <>
      <div>{eltWidth}x{eltHeight}</div>
      <textarea ref={ref}></textarea>
    </>
  );
}

En live ça donne ça :

Démonstration sous forme de gif de la manipulation du textarea décrit dans le bloc de code juste au-dessus

Conclusion

Les ResizeObserver ne sont pas très compliqués à manipuler, mais ce n'est pas la manière la plus naturelle d'aller chercher un événement. Côté React on voit assez vite la complexité du modèle de React ajouter sa patte mais dans la pratique c'est relativement simple à utiliser quand même !

Sources:

Crédit photo : https://unsplash.com/fr/photos/KJaI9wD8RjA