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 notificações sobre qualquer mudança nas dimensões da viewport. No gerenciador de eventos, você precisa descobrir quais elementos foram afetados por essa mudança e chamar uma rotina específica para reagir adequadamente. Se você precisava das novas dimensões de um elemento após um redimensionamento, precisava chamar getBoundingClientRect() ou getComputedStyle(), o que pode causar o uso excessivo do layout se você não fizer o agrupamento de todas as leituras e todas as gravações.

Isso não cobria nem mesmo os casos em que os elementos mudavam de tamanho sem que a janela principal fosse 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, seus irmãos ou ancestrais.

É por isso que ResizeObserver é um primitivo útil. Ele reage a mudanças no tamanho de qualquer um dos elementos observados, independente do que causou a mudança. Ele também oferece acesso ao novo tamanho dos elementos observados.

Compatibilidade com navegadores

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

Origem

API

Todas as APIs com o sufixo Observer mencionado acima compartilham um design simples. ResizeObserver não é uma exceção. Você cria um objeto ResizeObserver e transmite um callback para o 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 por meio de uma propriedade chamada contentRect, que retorna um objeto DOMRectReadOnly. A caixa de conteúdo é a caixa em que o conteúdo pode ser colocado. É a caixa de borda menos o padding.

Um diagrama do modelo de caixa do CSS.

É importante observar que, embora o ResizeObserver informe as dimensões do contentRect e do padding, ele só monitora o contentRect. Não confunda contentRect com a caixa delimitadora do elemento. A caixa delimitadora, conforme informada 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, o 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 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, esperamos 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á oferece suporte para as duas primeiras.

Quando isso está sendo relatado?

A especificação proíbe que ResizeObserver processe todos os eventos de redimensionamento antes da pintura e depois do layout. Isso faz com que o callback de um ResizeObserver seja o local ideal para fazer mudanças no layout da página. Como o processamento de ResizeObserver acontece entre o layout e a pintura, isso só invalida o layout, não a pintura.

Gotcha

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 profundo 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. Observando os elementos, você pode definir seus pontos de interrupção de design e mudar os estilos de um elemento. No exemplo abaixo, 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 típico de cima para baixo é o posicionamento do 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 muda de paisagem para retrato ou vice-versa) precisa fazer o mesmo.

O ResizeObserver permite escrever um único trecho de código que cuida de ambos os cenários. O redimensionamento da 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 criar 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 é?

A partir daí, posso adicionar mais código para lidar com o caso em que o usuário rolou para cima manualmente e quer que a rolagem fique focada nesta 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 notificações quando as dimensões mudavam para que os filhos pudessem ser dispostos novamente.

Efeitos na interação com a próxima pintura (INP)

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

Embora o tempo que leva 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 da INP a ser considerado. A INP também considera o tempo necessário para que a próxima pintura da interação ocorra. Esse é o tempo que o trabalho de renderização necessário para atualizar a interface do usuário leva para ser concluído em resposta a uma interação.

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

Faça o mínimo de renderização necessário em um callback ResizeObserver, porque o trabalho de renderização excessivo pode criar situações em que o navegador atrasará o trabalho importante. Por exemplo, se qualquer interação tiver um callback que faz com que um callback ResizeObserver seja executado, faça o seguinte para facilitar a melhor experiência possível:

  • Confira se os seletores de CSS são o mais simples possível para evitar trabalho excessivo de recalculo de estilo. Os recalculos de estilo ocorrem pouco antes do layout, e seletores CSS complexos podem atrasar as operações de layout.
  • Evite fazer qualquer trabalho no callback ResizeObserver que possa acionar refluxos forçados.
  • O tempo necessário para atualizar o layout de uma página geralmente aumenta com o número de elementos DOM nela. Embora isso seja verdade, independentemente de as páginas usarem ResizeObserver, 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 deles. Tenha cuidado para não atrasar muito a renderização com essa API poderosa.