What is the difference between resize event and resize observer?

Resize Event vs. ResizeObserver: ¿Cuál elegir?

hace 10 años

★★★★★Valoración: 4.48 (6644 votos)

En el mundo del desarrollo web moderno, crear diseños que se adapten a cualquier tamaño de pantalla es fundamental. A menudo, esto implica la necesidad de detectar cuándo el usuario redimensiona la ventana de su navegador o cambia de orientación en un dispositivo móvil. Tradicionalmente, los desarrolladores recurrían al evento resize del objeto window. Sin embargo, han surgido APIs más modernas y eficientes que ofrecen un mejor rendimiento y una mayor precisión. En este artículo, exploraremos las diferencias entre el evento resize y ResizeObserver, analizaremos los problemas de rendimiento asociados al evento resize y presentaremos alternativas más robustas para manejar los cambios de tamaño en tu sitio web.

How to listen to window resize?
Answer: Use the addEventListener() Method You can simply use the addEventListener() method to register an event handler to listen for the browser window resize event, such as window. addEventListener('resize', ...) . The following example will display the current width and height of the browser window on resize.
Índice de Contenido

¿Qué problemas presenta el evento resize?

Aunque el evento resize ha sido una herramienta común durante mucho tiempo, presenta algunas limitaciones importantes que pueden afectar negativamente el rendimiento de tu aplicación web.

Frecuencia de disparo excesiva

El principal inconveniente del evento resize es su alta frecuencia de disparo. Se activa continuamente mientras el usuario está redimensionando la ventana, incluso antes de que suelte el botón del ratón. Por cada píxel que cambia la ventana al arrastrar, se genera un evento. Esto puede resultar en cientos de llamadas a tu función de escucha de eventos, lo que puede sobrecargar el hilo principal del navegador y degradar el rendimiento general de la página.

Observa este ejemplo:

window.addEventListener('resize', () => { console.log(`Ancho: ${window.innerWidth}px, Alto: ${window.innerHeight}px`); }); 

Si ejecutas este código y redimensionas la ventana, verás en la consola una gran cantidad de mensajes generándose rápidamente, incluso con pequeños ajustes de tamaño.

Potencial para el Layout Thrashing

La situación empeora si, dentro del manejador del evento resize, incluyes lógica para leer las dimensiones de un elemento en la pantalla, como con element.clientWidth, element.getBoundingClientRect() o getComputedStyle(). Leer el tamaño de un elemento fuerza al navegador a calcular su tamaño de forma inmediata y síncrona. Este cálculo puede ser costoso, ya que obliga a toda la página a recalcular el diseño y los estilos.

Si luego realizas cambios en la pantalla que afecten al diseño, este se invalida nuevamente y el navegador no puede almacenar en caché el tamaño calculado previamente. La próxima vez que leas el tamaño del elemento, el navegador tendrá que recalcularlo por completo.

Este fenómeno se conoce como layout thrashing, y es un cuello de botella común en el rendimiento web. El thrashing dentro de un listener de eventos resize puede desencadenarse cientos de veces por segundo, lo que puede ralentizar significativamente el navegador e incluso provocar que se congele.

Aquí un ejemplo que ilustra el layout thrashing:

window.addEventListener('resize', () => { // offsetWidth lee el tamaño del elemento, forzando el layout // Establecer una propiedad de estilo invalida el layout, por lo que no se almacena en caché // Esto puede suceder cientos de veces por segundo al redimensionar circle.style.width = `${box.offsetWidth}px`; }); 

Alternativas al evento resize para un mejor rendimiento

Afortunadamente, existen alternativas más eficientes y modernas al evento resize que puedes utilizar para manejar los cambios de tamaño en tu web.

1. Media Queries (Consultas de Medios)

La mejor manera de evitar los problemas del evento resize es, cuando sea posible, evitarlo por completo. Si tu lógica se basa principalmente en adaptar el diseño en función del tamaño de la ventana, las media queries de CSS son la solución ideal.

Las media queries te permiten definir reglas CSS específicas que solo se aplican cuando la ventana del navegador cumple ciertas condiciones de tamaño. Esto permite adaptar el estilo de tu página de forma declarativa y eficiente, sin necesidad de Javascript para los casos más comunes.

Ejemplo de media queries en CSS:

body { /* Fondo azul oscuro por defecto */ background: #032030; } @media (max-width: 400px) { body { /* Fondo negro cuando la ventana tiene menos de 400 píxeles de ancho */ background: #000; } } 

Si necesitas utilizar media queries en Javascript, puedes usar la API matchMedia. Esta API te permite evaluar condiciones de media queries en Javascript y comprobar si se cumplen. Puedes verificar el valor actual con query.matches y escuchar los cambios de tamaño con el evento "change".

A diferencia del evento "resize", el evento "change" solo se dispara cuando se cruza un punto de interrupción definido en la media query. Esto significa que se activa mucho menos frecuentemente y solo cuando es realmente necesario.

Ejemplo de matchMedia en Javascript:

const query = matchMedia('(max-width: 400px)'); // ¿El ancho inicial de la ventana es menor a 400 píxeles? console.log(query.matches); // Escuchar cada vez que cambia query.matches query.addEventListener('change', (event) => { // ¿El ancho actual de la ventana es menor a 400 píxeles? console.log(event.matches); }); 

En resumen, las media queries son la mejor opción si tu objetivo es realizar cambios basados en el tamaño de la ventana, ofreciendo el mejor rendimiento y la menor complejidad.

How to use window resizeTo?
resizeTo() function: window. resizeTo(450, 300); The above example will resize the window to a width of 450 pixels and height of 300 pixels.

2. ResizeObserver

Si bien las media queries son ideales para reaccionar al tamaño de la ventana, no son útiles si necesitas realizar cambios basados en el tamaño de un elemento específico. Por ejemplo, si tienes un componente que puede aparecer en diferentes ubicaciones de la página, su ancho puede variar dependiendo del contexto, y depender del tamaño de la ventana no sería adecuado.

Además, los elementos pueden cambiar de tamaño incluso sin que el usuario redimensione la ventana. Considera una barra lateral que se abre y se cierra: el contenido principal adyacente cambiará de ancho, pero el tamaño de la ventana permanecerá igual.

Para estos casos, la API ResizeObserver es la solución perfecta. ResizeObserver reacciona a los cambios de tamaño de cualquier elemento observado, independientemente de la causa del cambio. Esto incluye cambios de tamaño provocados por el redimensionamiento de la ventana, cambios en el contenido del elemento, cambios de estilo, etc.

Una ventaja clave de ResizeObserver sobre el evento resize es que te proporciona el nuevo tamaño del elemento directamente en el callback. No necesitas leer el tamaño manualmente, lo que evita el layout thrashing y mejora el rendimiento, ya que el navegador puede optimizar la lectura del tamaño del elemento.

Ejemplo de uso de ResizeObserver:

const resizeObserver = new ResizeObserver((entries) => { // entries es un array de objetos ResizeObserverEntry // Nos interesa solo el primero, ya que observamos un solo elemento en este ejemplo // Si observas múltiples elementos, habrá múltiples entradas en este array const entry = entries[0]; // Imprimir el nuevo tamaño del elemento console.log(entry.contentBoxSize); }); resizeObserver.observe(elemento); 

ResizeObserver te proporciona información tanto de borderBoxSize (tamaño del elemento incluyendo padding y bordes) como de contentBoxSize (solo el contenido del elemento, sin padding ni bordes), permitiéndote elegir la medida más adecuada para tus necesidades.

Margen | Borde | Relleno | Contenido
Border Box | Content Box

En resumen, ResizeObserver es la mejor opción si necesitas reaccionar a cambios de tamaño de elementos individuales, ofreciendo un rendimiento superior y mayor precisión que el evento resize.

3. Container Queries (Consultas de Contenedor)

Aunque las container queries son aún una tecnología relativamente nueva, representan una prometedora alternativa basada en CSS a ResizeObserver y al evento "resize". Si bien aún no son ampliamente adoptadas en todos los proyectos, para aplicaciones web modernas o proyectos futuros, las container queries ofrecen una solución elegante y eficiente.

Las container queries comparten una sintaxis similar a las media queries, pero en lugar de basarse en el tamaño de la ventana, se basan en el tamaño del elemento contenedor al que se aplican. Esto permite un diseño más modular y encapsulado, donde los estilos de un componente se adaptan al tamaño de su contenedor, independientemente del tamaño de la ventana.

Ejemplo de container queries:

.contenedor-padre { container-type: inline-size; } @container (max-width: 400px) { .elemento { /* Fondo negro cuando el contenedor padre tiene menos de 400 píxeles de ancho */ background: #000; } } 

Si no necesitas leer el tamaño de los elementos en Javascript, las container queries son una excelente opción. Como regla general, cuanto menos Javascript utilices para el diseño, mejor será el rendimiento de tu aplicación web.

¿Qué desencadena ResizeObserver?

ResizeObserver te notifica cuando cambia el tamaño de un elemento observado. Como se mencionó anteriormente, esto puede ocurrir por diversas razones:

  • Redimensionamiento de la ventana del navegador por parte del usuario.
  • Cambios en el contenido del elemento observado.
  • Cambios en el estilo del elemento o de sus ancestros que afecten a su tamaño.
  • Adición o eliminación de elementos hijos.

A diferencia del evento resize del documento, que solo se activa al redimensionar la ventana, ResizeObserver es mucho más versátil y preciso, ya que reacciona a cualquier cambio que afecte al tamaño de un elemento observado, independientemente de la causa.

Un aspecto importante de ResizeObserver es que procesa todos los eventos de cambio de tamaño antes del despliegue y después del diseño. Esto significa que el callback de ResizeObserver es el lugar ideal para realizar cambios en el diseño de tu página. Al ejecutarse entre el diseño y el despliegue, las modificaciones realizadas en el callback solo invalidarán el diseño, pero no el despliegue, lo que contribuye a un mejor rendimiento.

Bucles de Callback Infinitos

Podrías preguntarte: ¿qué sucede si cambio el tamaño de un elemento observado dentro del callback de ResizeObserver? La respuesta es que se activará otra invocación al callback inmediatamente. Sin embargo, ResizeObserver incluye un mecanismo para evitar bucles de callback infinitos y dependencias cíclicas.

Los cambios solo se procesarán en el mismo fotograma si el elemento redimensionado está más profundo en el árbol DOM que el elemento menos profundo procesado en el callback anterior. De lo contrario, se aplazarán hasta el siguiente fotograma. Este mecanismo inteligente garantiza la estabilidad y evita bloqueos en la ejecución.

How to resize a div dynamically?
Resize a DIV using CSS. The following CSS adds a resize grip similar to a textarea which allows the size of the div to be changed by the user.

API ResizeObserver - Detalles técnicos

Todas las APIs con el sufijo Observer comparten un diseño de API similar, y ResizeObserver no es una excepción. Para utilizarlo, se crea un objeto ResizeObserver y se le pasa una función de callback al constructor. A esta función de callback se le pasa un array de objetos ResizeObserverEntry, donde cada entrada corresponde a un elemento observado y contiene información sobre su nuevo tamaño.

Ejemplo de creación y uso de ResizeObserver:

var ro = new ResizeObserver(entries => { for (let entry of entries) { const cr = entry.contentRect; console.log('Elemento:', entry.target); console.log(`Tamaño del elemento: ${cr.width}px x ${cr.height}px`); console.log(`Padding del elemento: ${cr.top}px; ${cr.left}px`); } }); // Observar uno o varios elementos ro.observe(elementoObservado); 

¿Qué información se reporta?

Generalmente, un ResizeObserverEntry informa sobre el cuadro de contenido de un elemento a través de la propiedad contentRect, que devuelve un objeto DOMRectReadOnly. El cuadro de contenido es el área donde se puede colocar el contenido del elemento, excluyendo el relleno y los bordes.

Es importante destacar que, aunque ResizeObserver informa tanto las dimensiones del contentRect como del relleno, solo observa el contentRect. No confundas contentRect con el cuadro delimitador (bounding box) del elemento, obtenido con getBoundingClientRect(). El cuadro delimitador incluye el elemento completo y sus descendientes. Los SVG son una excepción, donde ResizeObserver informa las dimensiones del cuadro delimitador.

A partir de Chrome 84, ResizeObserverEntry incluye tres nuevas propiedades para proporcionar información más detallada, cada una devolviendo un objeto ResizeObserverSize con propiedades blockSize y inlineSize:

  • borderBoxSize: Tamaño del border box.
  • contentBoxSize: Tamaño del content box.
  • devicePixelContentBoxSize: Tamaño del content box en píxeles de dispositivo.

Estas propiedades devuelven arrays de solo lectura, anticipando la futura compatibilidad con elementos que puedan tener múltiples fragmentos, como en escenarios de múltiples columnas. Actualmente, estos arrays solo contienen un elemento.

Aplicaciones prácticas de ResizeObserver

ResizeObserver abre un abanico de posibilidades para crear componentes web más dinámicos y adaptables. Veamos algunos ejemplos de aplicaciones prácticas:

Media Queries por elemento

Una aplicación directa de ResizeObserver es implementar media queries por elemento. Observando elementos individuales, puedes definir puntos de interrupción específicos para su diseño y cambiar sus estilos de forma imperativa en Javascript, basándote en su propio tamaño, no en el de la ventana.

Ejemplo: cambiar el radio de borde de un elemento en función de su ancho:

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

Ventana de chat con scroll automático

En una ventana de chat con mensajes que se muestran de arriba a abajo, es común que el usuario quiera que la ventana se mantenga scrolleada hasta el final para ver los mensajes más recientes. Además, los cambios de diseño (como cambiar la orientación de un dispositivo móvil) también deberían mantener el scroll en la parte inferior.

ResizeObserver permite implementar esta funcionalidad de forma elegante y eficiente, manejando tanto los cambios de tamaño de la ventana como la adición de nuevos mensajes:

const ro = new ResizeObserver(entries => { document.scrollingElement.scrollTop = document.scrollingElement.scrollHeight; }); // Observar el scrollingElement cuando la ventana se redimensiona ro.observe(document.scrollingElement); // Observar la línea de tiempo (contenedor de mensajes) para procesar nuevos mensajes. ro.observe(timeline); 

Elementos personalizados (Custom Elements)

Para desarrolladores que crean elementos personalizados (Custom Elements), ResizeObserver proporciona una forma fiable de recibir notificaciones cuando cambian las dimensiones del elemento. Esto permite que el elemento personalizado pueda recalcular el diseño de sus hijos y adaptarse dinámicamente a los cambios de tamaño.

Manejo del resize de ventana con React Hooks

Si estás utilizando React, puedes manejar el resize de la ventana de forma efectiva utilizando React Hooks, específicamente useEffect y useState. Estos hooks te permiten gestionar el estado y los efectos secundarios en componentes funcionales de React, creando componentes que reaccionan dinámicamente a los cambios de tamaño de la ventana.

Pasos para manejar el resize de ventana con Hooks:

  1. Gestión de Estado: Define variables de estado con useState para almacenar las dimensiones de la ventana, por ejemplo, windowWidth y windowHeight.
  2. Listener de Eventos: Dentro de useEffect, añade un listener de eventos para el evento resize en el objeto window. Este evento se disparará cada vez que cambie el tamaño de la ventana.
  3. Actualización del Estado: Cuando ocurra el evento resize, actualiza las variables de estado con las nuevas dimensiones de la ventana.
  4. Limpieza: Es crucial limpiar el listener de eventos cuando el componente se desmonta para evitar fugas de memoria. Retorna una función de limpieza desde useEffect para lograr esto.

Ejemplo de manejo de resize de ventana con React Hooks:

import React, { useState, useEffect } from 'react'; const App = () => { const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight, }); useEffect(() => { const handleResize = () => { setWindowSize({ width: window.innerWidth, height: window.innerHeight, }); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; }, []); return ( <div> <h2>Tamaño de la Ventana</h2> <p>Ancho: {windowSize.width}</p> <p>Alto: {windowSize.height}</p> </div> ); }; export default App; 

Conclusión

En resumen, si bien el evento resize ha sido una herramienta tradicional para manejar los cambios de tamaño, ResizeObserver emerge como una alternativa superior en términos de rendimiento, precisión y versatilidad. ResizeObserver evita los problemas de frecuencia excesiva y layout thrashing del evento resize, y ofrece una forma más granular de reaccionar a los cambios de tamaño de elementos individuales, no solo de la ventana.

Para casos donde la adaptación del diseño se basa principalmente en el tamaño de la ventana, las media queries de CSS siguen siendo la opción más eficiente y recomendada. Para necesidades más complejas, como reaccionar al tamaño de elementos específicos o crear componentes web más dinámicos, ResizeObserver es la API moderna y robusta que debes elegir.

ResizeObserver cuenta con una amplia compatibilidad en los navegadores modernos. Existen polyfills disponibles para navegadores más antiguos, aunque es importante tener en cuenta que no pueden replicar completamente la funcionalidad nativa de ResizeObserver, y pueden tener limitaciones de rendimiento o afectar al DOM de la página.

Subir