ResizeObserver: é como document.onresize para elementos.

O ResizeObserver informa quando o tamanho de um elemento muda.

Antes do ResizeObserver, era necessário anexar um listener ao evento resize do documento para receber uma notificação sobre qualquer mudança nas dimensões da janela de visualização. No manipulador de eventos, você precisaria descobrir quais elementos foram afetados por essa mudança e chamar uma rotina específica para reagir adequadamente. Se você precisasse das novas dimensões de um elemento após um redimensionamento, era necessário chamar getBoundingClientRect() ou getComputedStyle(), o que pode causar thrashing de layout se você não cuidar do agrupamento em lote de todas as leituras e todas as gravações.

Isso nem sequer abordou casos em que os elementos mudam de tamanho sem que a janela principal seja redimensionada. Por exemplo, anexar novos filhos, definir o estilo display de um elemento como none ou ações semelhantes podem mudar o tamanho de um elemento, dos irmãos ou dos ancestrais dele.

Por isso, ResizeObserver é uma primitiva útil. Ele reage a mudanças no tamanho de qualquer um dos elementos observados, independente do que causou a mudança. Ele também dá acesso ao novo tamanho dos elementos observados.

Browser Support

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

Source

API

Todas as APIs com o sufixo Observer mencionadas acima compartilham um design de API simples. ResizeObserver não é uma exceção. Você cria um objeto ResizeObserver e transmite uma callback ao construtor. O callback recebe uma matriz de objetos ResizeObserverEntry (uma entrada por elemento observado) que contém as novas dimensões do 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);

Alguns detalhes

O que está sendo denunciado?

Em geral, um ResizeObserverEntry informa a caixa de conteúdo de um elemento usando uma propriedade chamada contentRect, que retorna um objeto DOMRectReadOnly. A caixa de conteúdo é onde o conteúdo pode ser colocado. É a caixa de borda menos o padding.

Um diagrama do modelo de caixa CSS.

É importante observar que, embora o ResizeObserver informe as dimensões do contentRect e o padding, ele apenas monitora o contentRect. Não confunda contentRect com a caixa delimitadora do elemento. A caixa delimitadora, conforme informado por getBoundingClientRect(), é a caixa que contém todo o elemento e seus descendentes. Os SVGs são uma exceção à regra, em que ResizeObserver informa as dimensões da caixa delimitadora.

A partir do Chrome 84, ResizeObserverEntry tem três novas propriedades para fornecer informações mais detalhadas. Cada uma dessas propriedades retorna um objeto ResizeObserverSize que contém uma propriedade blockSize e uma propriedade inlineSize. Essas informações são sobre o elemento observado no momento em que o callback é invocado.

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

Todos esses itens retornam matrizes somente leitura porque, no futuro, espera-se que eles possam oferecer suporte a elementos com vários fragmentos, que ocorrem em cenários de várias colunas. Por enquanto, essas matrizes vão conter apenas um elemento.

O suporte da plataforma para essas propriedades é limitado, mas o Firefox já é compatível com as duas primeiras.

Quando isso está sendo informado?

A especificação determina que ResizeObserver processe todos os eventos de redimensionamento antes da renderização e depois do layout. Isso torna o callback de um ResizeObserver o lugar ideal para fazer mudanças no layout da página. Como o processamento de ResizeObserver acontece entre o layout e a renderização, isso invalida apenas o layout, não a renderização.

Entendi

Você pode estar se perguntando: o que acontece se eu mudar o tamanho de um elemento observado dentro do callback para ResizeObserver? A resposta é: você vai acionar outra chamada para o callback imediatamente. Felizmente, o ResizeObserver tem um mecanismo para evitar loops de callback infinitos e dependências cíclicas. As mudanças só serão processadas no mesmo frame se o elemento redimensionado estiver mais abaixo na árvore DOM do que o elemento mais superficial processado no callback anterior. Caso contrário, eles serão adiados para o próximo frame.

Aplicativo

Uma das coisas que o ResizeObserver permite fazer é implementar consultas de mídia por elemento. Ao observar elementos, é possível definir de forma imperativa os pontos de interrupção do design e mudar os estilos de um elemento. No exemplo a seguir, a segunda caixa muda o raio da borda de acordo com a largura.

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

Outro exemplo interessante é uma janela de chat. O problema que surge em um layout de conversa de cima para baixo típico é o posicionamento da rolagem. Para evitar confundir o usuário, é útil que a janela fique na parte de baixo da conversa, onde as mensagens mais recentes aparecem. Além disso, qualquer tipo de mudança de layout (pense em um smartphone que passa do modo paisagem para retrato ou vice-versa) deve alcançar o mesmo resultado.

Com o ResizeObserver, é possível escrever um único trecho de código que cuida dos dois cenários. Redimensionar a janela é um evento que um ResizeObserver pode capturar por definição, mas chamar appendChild() também redimensiona esse elemento (a menos que overflow: hidden esteja definido), porque ele precisa abrir espaço para os novos elementos. Com isso em mente, são necessárias poucas linhas para alcançar o efeito desejado:

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

Muito legal, não é?

Aqui, posso adicionar mais código para processar o caso em que o usuário rolou manualmente para cima e quer que a rolagem fique na mesma mensagem quando uma nova mensagem chegar.

Outro caso de uso é para qualquer tipo de elemento personalizado que esteja fazendo o próprio layout. Até ResizeObserver, não havia uma maneira confiável de receber uma notificação quando as dimensões mudam para que os filhos possam ser dispostos novamente.

Efeitos na métrica Interaction to Next Paint (INP)

A Interaction to Next Paint (INP) é uma métrica que mede a capacidade de resposta geral de uma página às interações do usuário. Se o INP de uma página estiver no limiar "bom", ou seja, 200 milissegundos ou menos, é possível dizer que ela responde de maneira confiável às interações do usuário.

Embora o tempo necessário para que os callbacks de eventos sejam executados em resposta a uma interação do usuário possa contribuir significativamente para a latência total de uma interação, esse não é o único aspecto do INP a ser considerado. A INP também considera o tempo necessário para que a próxima renderização da interação ocorra. Esse é o tempo necessário para que o trabalho de renderização exigido para atualizar a interface do usuário em resposta a uma interação seja concluído.

No caso de ResizeObserver, isso é importante porque o callback que uma instância ResizerObserver executa ocorre pouco antes do trabalho de renderização. Isso é proposital, já que o trabalho realizado no callback precisa ser levado em consideração, porque o resultado desse trabalho provavelmente vai exigir uma mudança na interface do usuário.

Faça o mínimo de trabalho de renderização possível em um callback ResizeObserver, já que o excesso pode criar situações em que o navegador atrasa tarefas importantes. Por exemplo, se alguma interação tiver um callback que faça um callback ResizeObserver ser executado, faça o seguinte para facilitar a melhor experiência possível:

  • Verifique se os seletores de CSS são o mais simples possível para evitar trabalho excessivo de recálculo de estilo. Os recálculos de estilo ocorrem pouco antes do layout, e seletores de CSS complexos podem atrasar as operações de layout.
  • Evite fazer qualquer trabalho no callback ResizeObserver que possa acionar reflows forçados.
  • O tempo necessário para atualizar o layout de uma página geralmente aumenta com o número de elementos DOM nela. Isso é verdade se as páginas usam ou não ResizeObserver, mas o trabalho feito em um callback ResizeObserver pode se tornar significativo à medida que a complexidade estrutural de uma página aumenta.

Conclusão

O ResizeObserver está disponível em todos os principais navegadores e oferece uma maneira eficiente de monitorar redimensionamentos de elementos no nível do elemento. Mas tenha cuidado para não atrasar muito a renderização com essa API eficiente.