API ResizeObserver do JavaScript
Aprenda a API ResizeObserver do JavaScript para reagir a mudanças de tamanho de elementos — componentes responsivos, canvas, gráficos e inputs adaptáveis.
A API ResizeObserver permite reagir quando um elemento específico muda de tamanho, independentemente do motivo. Ela pertence à mesma família do MutationObserver (que observa a árvore DOM) e do IntersectionObserver (que observa a visibilidade): observadores eficientes baseados em callbacks que substituem loops de polling inconvenientes.
Por Que Não Usar o Evento resize?
O evento resize da janela só dispara quando o tamanho da janela do navegador muda. Mas um elemento na página pode mudar de tamanho por muitas outras razões:
- Uma regra CSS muda (uma media query entra em ação, uma classe é alternada).
- Seu conteúdo muda (texto é carregado, uma imagem chega, linhas são adicionadas a uma lista).
- Um layout Flexbox ou CSS Grid é reformatado porque um elemento irmão cresceu ou diminuiu.
- Um contêiner pai é redimensionado por um divisor arrastável, uma barra lateral ou um painel.
Nenhuma dessas situações necessariamente redimensiona a janela, portanto window.addEventListener('resize', ...) nunca dispara. Você poderia fazer polling com setInterval e comparar getBoundingClientRect() a cada poucos milissegundos, mas isso desperdiça CPU e ainda fica atrás da mudança real.
ResizeObserver resolve isso: ele observa um ou mais elementos e chama seu callback apenas quando o tamanho deles realmente muda.
ResizeObserver reporta o tamanho do conteúdo de um elemento por padrão, não sua posição. Se você precisar saber quando um elemento se move ou entra no viewport, use o IntersectionObserver. Para medições no nível do viewport, consulte tamanhos de janela e rolagem.
Uso Básico
O padrão espelha os outros observadores: crie um observador com um callback e, em seguida, diga quais elemento(s) ele deve observe.
const box = document.querySelector('#box');
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`${entry.target.id} is now ${width} x ${height}`);
}
});
ro.observe(box);O callback recebe um array de entradas — uma por elemento observado que mudou neste lote — portanto, um único observador pode monitorar muitos elementos ao mesmo tempo.
O Que Há Dentro de uma Entrada
Cada entrada descreve um elemento e seu novo tamanho. Existem duas formas de ler esse tamanho.
A maneira mais simples é entry.contentRect, um DOMRectReadOnly com width, height, top e left (os deslocamentos são relativos ao box de padding do elemento):
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
console.log(entry.target); // the element
console.log(entry.contentRect.width); // content-box width in px
console.log(entry.contentRect.height); // content-box height in px
}
});A maneira mais precisa usa as propriedades de tamanho de box, que são arrays de objetos { inlineSize, blockSize } (um array porque um elemento pode ser fragmentado entre colunas):
entry.contentBoxSize— o content box, excluindo padding e border.entry.borderBoxSize— o border box, incluindo padding e border.entry.devicePixelContentBoxSize— o content box medido em pixels de dispositivo, ideal para renderização nítida de canvas em telas de alto DPI.
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
// inlineSize ~ width, blockSize ~ height (for a horizontal writing mode)
const { inlineSize, blockSize } = entry.borderBoxSize[0];
console.log(`border box: ${inlineSize} x ${blockSize}`);
}
});Por padrão, o observador reage a mudanças no content box. Para observar o border box em vez disso, passe um objeto de opções:
ro.observe(box, { box: 'border-box' });inlineSize e blockSize são conscientes do modo de escrita. No modo padrão da esquerda para a direita, de cima para baixo, inlineSize é a largura e blockSize é a altura — mas em um modo de escrita vertical eles são invertidos. Prefira-os em vez de width/height fixos quando sua interface precisar suportar múltiplas direções de escrita.
Métodos
ResizeObserver expõe três métodos:
observe(element, options)— começar a observar um elemento. Chame uma vez por elemento. Ooptions.boxopcional pode ser'content-box'(padrão),'border-box'ou'device-pixel-content-box'.unobserve(element)— parar de observar um único elemento.disconnect()— parar de observar todos os elementos de uma vez.
ro.observe(el); // start
ro.unobserve(el); // stop watching this one
ro.disconnect(); // stop watching everythingCaso de Uso 1 — Componentes Responsivos ("Container Queries em JS")
Uma media query reage ao viewport. Mas um card reutilizável pode ser largo na coluna principal e estreito em uma barra lateral com a mesma largura de viewport. Com ResizeObserver você pode adaptar um componente à sua própria largura:
const card = document.querySelector('.card');
const ro = new ResizeObserver(([entry]) => {
const width = entry.contentRect.width;
// Toggle a layout class based on the element's own width.
card.classList.toggle('card--compact', width < 400);
});
ro.observe(card);.card { display: flex; gap: 1rem; }
.card--compact { flex-direction: column; }Agora o card se empilha verticalmente sempre que ele tiver menos de 400px de largura, independentemente do tamanho da janela — útil em painéis, divisões de tela e widgets incorporáveis.
O CSS moderno pode fazer isso nativamente com container queries (@container). Se você só precisa reestilizar com base no tamanho de um contêiner, prefira as container queries do CSS — elas são declarativas e executam fora da thread principal. Use ResizeObserver quando precisar executar JavaScript em resposta à mudança de tamanho (recalcular valores, redesenhar um canvas, reformatar um gráfico).
Caso de Uso 2 — Redimensionando um Canvas ou Gráfico
Um <canvas> tem dois tamanhos: seu tamanho de exibição CSS e o tamanho do buffer de desenho (canvas.width/canvas.height). Se eles não coincidirem, o bitmap é esticado e parece borrado. ResizeObserver mantém o buffer sincronizado com o tamanho exibido para que os desenhos permaneçam nítidos:
const canvas = document.querySelector('#chart');
const ctx = canvas.getContext('2d');
const ro = new ResizeObserver(([entry]) => {
// Use device-pixel size for sharp rendering on high-DPI screens.
const size = entry.devicePixelContentBoxSize?.[0];
const width = size ? size.inlineSize : entry.contentRect.width;
const height = size ? size.blockSize : entry.contentRect.height;
canvas.width = width;
canvas.height = height;
redraw(ctx, width, height); // your drawing / charting code
});
ro.observe(canvas, { box: 'device-pixel-content-box' });A mesma ideia se aplica a bibliotecas de gráficos: observe o contêiner do gráfico e chame o método resize() da biblioteca quando o contêiner mudar, em vez de depender apenas de window.resize.
Caso de Uso 3 — Textarea com Crescimento Automático
Como o callback dispara sempre que o tamanho medido do elemento muda, você pode manter elementos dependentes sincronizados. Um exemplo clássico é espelhar a altura de uma textarea para um elemento irmão, ou reagir ao crescimento impulsionado pelo conteúdo:
const textarea = document.querySelector('#message');
const counter = document.querySelector('#height-readout');
const ro = new ResizeObserver(([entry]) => {
const h = Math.round(entry.contentRect.height);
counter.textContent = `${h}px tall`;
});
ro.observe(textarea);Sempre que o usuário arrastar o handle de redimensionamento da textarea — ou seu código alterar a altura enquanto o usuário digita — a leitura é atualizada automaticamente, sem evento resize e sem polling.
O Aviso "ResizeObserver loop"
Você pode se deparar com esta mensagem no console:
ResizeObserver loop completed with undelivered notifications.
(Alguns navegadores formulam como "ResizeObserver loop limit exceeded.") Isso acontece quando seu callback altera o tamanho do elemento observado, o que aciona o observador novamente, que altera o tamanho novamente — um loop de feedback.
// Anti-pattern: this can cause the loop warning.
const ro = new ResizeObserver(([entry]) => {
// Resizing the observed element from inside its own callback. Bad.
entry.target.style.height = entry.contentRect.width + 'px';
});Não redimensione sincronamente o elemento observado dentro de seu próprio callback. Se um redimensionamento baseado em tamanho for inevitável, quebre o ciclo: escreva apenas quando o valor realmente mudar (uma guarda), ou adie a escrita com requestAnimationFrame. O aviso geralmente é inofensivo e se autocorrige, mas um loop infinito verdadeiro prejudicará o desempenho.
Limpeza
Um observador mantém uma referência a cada elemento que observa, o que pode impedir que esse elemento seja coletado pelo garbage collector. Sempre pare de observar quando terminar — por exemplo, quando um componente for desmontado ou uma view for destruída:
function mountWidget(el) {
const ro = new ResizeObserver(handleResize);
ro.observe(el);
// Return a cleanup function.
return () => ro.disconnect();
}Esquecer isso é uma fonte comum de vazamentos de memória em aplicações de página única. Para mais informações sobre como manter o trabalho de tamanho e layout eficiente, consulte otimização de desempenho do DOM.
ResizeObserver é suportado em todos os navegadores modernos, portanto você pode contar com ele sem um polyfill em qualquer navegador evergreen atual.