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.
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.
É 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.
borderBoxSizecontentBoxSizedevicePixelContentBoxSize
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
ResizeObserverque 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 callbackResizeObserverpode 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.