W3docs

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.

Informação

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

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. O options.box opcional 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 everything

Caso 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.

Dica

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

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.

Teste Seu Conhecimento

Prática
Por que preferir ResizeObserver ao invés do evento 'resize' da janela para um componente?
Por que preferir ResizeObserver ao invés do evento 'resize' da janela para um componente?
Prática
O que cada entrada do ResizeObserver fornece?
O que cada entrada do ResizeObserver fornece?
Prática
O que geralmente aciona o aviso 'ResizeObserver loop'?
O que geralmente aciona o aviso 'ResizeObserver loop'?
Was this page helpful?