ResizeObserver: é como document.onresize para elementos.

ResizeObserver informa quando o tamanho de um elemento muda.

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

Isso nem mesmo abrangeu casos em que os elementos mudam 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, de seus irmãos ou de seus 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. Ela também fornece acesso ao novo tamanho dos elementos observados.

Compatibilidade com navegadores

  • 64
  • 79
  • 69
  • 13.1

Origem

API

Todas as APIs com o sufixo Observer mencionado acima têm um design simples. ResizeObserver não é exceção. Crie um objeto ResizeObserver e transmita 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?

Geralmente, um ResizeObserverEntry informa a caixa de conteúdo de um elemento com 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 box do CSS.

É importante observar que, embora ResizeObserver informe as dimensões do contentRect e do padding, ele apenas observa o contentRect. Não confunda contentRect com a caixa delimitadora do elemento. A caixa delimitadora, conforme informado por getBoundingClientRect(), é a caixa que contém o elemento inteiro e os descendentes dele. 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 ofereçam suporte a elementos com vários fragmentos, que ocorrerão em cenários com várias colunas. Por enquanto, essas matrizes conterão apenas um elemento.

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

Quando ela está sendo reportada?

A especificação determina que ResizeObserver precisa processar todos os eventos de redimensionamento antes da pintura e depois do layout. Isso faz do callback de um ResizeObserver o local ideal para fazer mudanças no layout da página. Como o processamento de ResizeObserver acontece entre layout e pintura, isso vai invalidar o layout, não a pintura.

Peguei você

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ê acionará outra chamada imediatamente para o callback. Felizmente, ResizeObserver tem um mecanismo para evitar loops de callback infinitos e dependências cíclicas. As alterações só serão processadas no mesmo frame se o elemento redimensionado estiver mais profundo na árvore DOM do que o elemento shallowest processado no callback anterior. Caso contrário, eles serão adiados para o próximo frame.

legado

Uma coisa que ResizeObserver permite que você faça é implementar consultas de mídia por elemento. Observando os 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 mudará o raio da borda de acordo com a largura dela.

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 bate-papo. O problema que surge em um layout de conversa de cima para baixo típico é o posicionamento de rolagem. Para evitar confundir o usuário, é recomendável que a janela permaneça no final da conversa, onde as mensagens mais recentes aparecem. Além disso, qualquer tipo de mudança de layout (pense em um smartphone que muda da orientação de paisagem para retrato ou vice-versa) terá o mesmo resultado.

ResizeObserver permite que você escreva um único código para lidar com os 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 liberar espaço para os novos elementos. Pensando nisso, são necessárias poucas linhas para alcançar o efeito pretendido:

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

Isso é muito legal, não é?

Aqui, é possível adicionar mais código para processar o caso em que o usuário rolou para cima manualmente e quer que a rolagem siga essa mensagem quando uma nova chegar.

Outro caso de uso é para qualquer tipo de elemento personalizado que tenha o próprio layout. Até ResizeObserver, não havia uma maneira confiável de ser notificado quando as dimensões mudassem para que os filhos pudessem ser dispostos novamente.

Efeitos na interação com a próxima exibição (INP, na sigla em inglês)

A Interação com a próxima exibição (INP, na sigla em inglês) é uma métrica que mede a capacidade geral de uma página em relação às interações do usuário. Se o INP de uma página estiver no limite "bom", ou seja, 200 milissegundos ou menos, é possível dizer que a página é responsiva de forma confiável às interações do usuário com ela.

O tempo necessário para executar callbacks de eventos em resposta a uma interação do usuário pode contribuir significativamente para a latência total de uma interação, mas esse não é o único aspecto do INP a ser considerado. O INP também considera o tempo necessário para a próxima exibição da interação. Esse é o tempo necessário para que o trabalho de renderização atualize a interface do usuário em resposta a uma interação para concluir.

Quando ResizeObserver está relacionado, isso é importante porque o callback que uma instância ResizerObserver executa ocorre logo antes do trabalho de renderização. Isso é intencional, já que o trabalho que ocorre no callback precisa ser considerado, porque o resultado desse trabalho vai exigir uma mudança na interface do usuário.

Realize o mínimo de trabalho de renderização necessário em um callback ResizeObserver, porque o excesso de renderização pode criar situações em que o navegador atrasa para realizar tarefas importantes. Por exemplo, se alguma interação tiver um callback que faça com que um callback ResizeObserver seja executado, faça o seguinte para facilitar a experiência mais tranquila 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 logo antes do layout, e seletores de CSS complexos podem atrasar operações de layout.
  • Evite realizar 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 de uma página. Isso é válido independentemente de as páginas usarem ou não ResizeObserver, mas o trabalho realizado 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 fornece uma maneira eficiente de monitorar redimensionamentos de elementos no nível do elemento. Só tome cuidado para não atrasar muito a renderização com essa API poderosa.