ResizeObserver: Es similar a document.onresize para los elementos.

ResizeObserver te permite saber cuando cambia el tamaño de un elemento.

Antes de ResizeObserver, debías adjuntar un objeto de escucha al evento resize del documento para recibir notificaciones sobre cualquier cambio en las dimensiones del viewport. En el controlador de eventos, tendrías que determinar qué elementos se vieron afectados por ese cambio y llamar a una rutina específica para reaccionar de forma adecuada. Si necesitabas las nuevas dimensiones de un elemento después de cambiar el tamaño, tenías que llamar a getBoundingClientRect() o getComputedStyle(), lo que puede provocar la hiperpaginación del diseño si no te encargas de agrupar en lotes todas las lecturas y todas las escrituras.

Esto ni siquiera abarcaba los casos en los que los elementos cambian de tamaño sin que se haya modificado el tamaño de la ventana principal. Por ejemplo, agregar nuevos elementos secundarios, establecer el estilo display de un elemento en none o acciones similares puede cambiar el tamaño de un elemento, sus elementos secundarios o sus principales.

Por eso, ResizeObserver es una primitiva útil. Reacciona a los cambios de tamaño de cualquiera de los elementos observados, independientemente de lo que haya causado el cambio. También proporciona acceso al nuevo tamaño de los elementos observados.

Navegadores compatibles

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

Origen

API

Todas las APIs con el sufijo Observer que mencionamos antes comparten un diseño de API simple. ResizeObserver no es una excepción. Creas un objeto ResizeObserver y le pasas una devolución de llamada al constructor. La devolución de llamada recibe un array de objetos ResizeObserverEntry (una entrada por elemento observado) que contiene las nuevas dimensiones del elemento.

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);

Algunos detalles

¿Qué se informa?

Por lo general, un ResizeObserverEntry informa el cuadro de contenido de un elemento a través de una propiedad llamada contentRect, que muestra un objeto DOMRectReadOnly. El cuadro de contenido es el cuadro en el que se puede colocar el contenido. Es el cuadro de borde menos el padding.

Diagrama del modelo de caja de CSS.

Es importante tener en cuenta que, si bien ResizeObserver informa las dimensiones de contentRect y el padding, solo mira el contentRect. No confundas contentRect con el cuadro de límite del elemento. El cuadro de límite, como lo informa getBoundingClientRect(), es el cuadro que contiene todo el elemento y sus descendientes. Los SVG son una excepción a la regla, en la que ResizeObserver informará las dimensiones del cuadro de límite.

A partir de Chrome 84, ResizeObserverEntry tiene tres propiedades nuevas para proporcionar información más detallada. Cada una de estas propiedades muestra un objeto ResizeObserverSize que contiene una propiedad blockSize y una propiedad inlineSize. Esta información se refiere al elemento observado en el momento en que se invoca la devolución de llamada.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

Todos estos elementos muestran arreglos de solo lectura porque, en el futuro, se espera que puedan admitir elementos que tengan varios fragmentos, lo que ocurre en situaciones de varias columnas. Por ahora, estos arrays solo contendrán un elemento.

La compatibilidad de la plataforma con estas propiedades es limitada, pero Firefox ya las admite.

¿Cuándo se informa?

La especificación prohíbe que ResizeObserver procese todos los eventos de cambio de tamaño antes de la pintura y después del diseño. Esto hace que la devolución de llamada de un ResizeObserver sea el lugar ideal para realizar cambios en el diseño de tu página. Debido a que el procesamiento de ResizeObserver se produce entre el diseño y la pintura, hacerlo solo invalidará el diseño, no la pintura.

Te atrapé

Es posible que te preguntes: ¿qué sucede si cambio el tamaño de un elemento observado dentro de la devolución de llamada a ResizeObserver? La respuesta es: Activarás otra llamada a la devolución de llamada de inmediato. Afortunadamente, ResizeObserver tiene un mecanismo para evitar bucles de devolución de llamada infinitos y dependencias cíclicas. Los cambios solo se procesarán en el mismo fotograma si el elemento cuyo tamaño se cambió está más abajo en el árbol DOM que el elemento más superficial procesado en la devolución de llamada anterior. De lo contrario, se diferirán al siguiente fotograma.

Aplicación

Una cosa que ResizeObserver te permite hacer es implementar consultas de medios por elemento. Mediante la observación de los elementos, puedes definir de manera imperativa los puntos de interrupción de diseño y cambiar los diseños de un elemento. En el siguiente ejemplo, el segundo cuadro cambiará su radio de borde según su ancho.

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)'));

Otro ejemplo interesante es una ventana de chat. El problema que surge en un diseño de conversación típico de arriba abajo es el posicionamiento del desplazamiento. Para evitar confundir al usuario, es útil que la ventana se mantenga al final de la conversación, donde aparecen los mensajes más recientes. Además, cualquier tipo de cambio de diseño (como en un teléfono que pase del modo horizontal al vertical, o viceversa) debería lograr lo mismo.

ResizeObserver te permite escribir un solo fragmento de código que se ocupa de ambos casos. Cambiar el tamaño de la ventana es un evento que un ResizeObserver puede capturar por definición, pero llamar a appendChild() también cambia el tamaño de ese elemento (a menos que se establezca overflow: hidden), ya que necesita hacer espacio para los elementos nuevos. Con esto en mente, se necesitan muy pocas líneas para lograr el efecto deseado:

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);

Genial, ¿no?

A partir de aquí, podría agregar más código para controlar el caso en el que el usuario desplazó el contenido hacia arriba de forma manual y desea que el desplazamiento se mantenga en ese mensaje cuando llegue uno nuevo.

Otro caso de uso es para cualquier tipo de elemento personalizado que tenga su propio diseño. Hasta ResizeObserver, no había una forma confiable de recibir notificaciones cuando cambiaban sus dimensiones para que sus elementos secundarios se pudieran volver a distribuir.

Efectos en la interacción con la siguiente pintura (INP)

Interaction to Next Paint (INP) es una métrica que mide la capacidad de respuesta general de una página ante las interacciones del usuario. Si el INP de una página está en el umbral de "bueno", es decir, 200 milisegundos o menos, se puede decir que una página responde de manera confiable a las interacciones del usuario con ella.

Si bien la cantidad de tiempo que tardan las devoluciones de llamada de eventos en ejecutarse en respuesta a una interacción del usuario puede contribuir de manera significativa a la latencia total de una interacción, ese no es el único aspecto de la INP que se debe tener en cuenta. La INP también considera la cantidad de tiempo que tarda en ocurrir la próxima pintura de la interacción. Es la cantidad de tiempo que se necesita para que el trabajo de renderización necesario actualizar la interfaz de usuario en respuesta a una interacción se complete.

Si se trata de ResizeObserver, esto es importante porque la devolución de llamada que ejecuta una instancia de ResizerObserver se produce justo antes del trabajo de renderización. Esto es así por diseño, ya que se debe tener en cuenta el trabajo que se produce en la devolución de llamada, ya que es muy probable que el resultado de ese trabajo requiera un cambio en la interfaz de usuario.

Ten cuidado de realizar la menor cantidad de trabajo de renderización posible en una devolución de llamada de ResizeObserver, ya que el trabajo de renderización excesivo puede crear situaciones en las que el navegador se retrasa en realizar tareas importantes. Por ejemplo, si alguna interacción tiene una devolución de llamada que ejecuta una devolución de llamada ResizeObserver, asegúrate de seguir estos pasos para facilitar la experiencia más fluida posible:

  • Asegúrate de que tus selectores de CSS sean lo más simples posible para evitar un trabajo excesivo de recálculo de estilo. Los recalculos de estilo se producen justo antes del diseño, y los selectores de CSS complejos pueden retrasar las operaciones de diseño.
  • Evita realizar cualquier trabajo en la devolución de llamada de ResizeObserver que pueda activar reflujos forzados.
  • El tiempo necesario para actualizar el diseño de una página generalmente aumenta con la cantidad de elementos DOM en una página. Si bien esto es así sin importar si las páginas usan ResizeObserver o no, el trabajo realizado en una devolución de llamada ResizeObserver puede volverse significativo a medida que aumenta la complejidad estructural de una página.

Conclusión

ResizeObserver está disponible en todos los navegadores principales y proporciona una forma eficiente de supervisar el cambio de tamaño de los elementos a nivel de estos. Solo ten cuidado de no retrasar demasiado la renderización con esta potente API.