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.
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.
É 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 callbackResizeObserver
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.