Os IntersectionObservers informam quando um elemento observado entra ou sai da janela de visualização do navegador.
Digamos que você queira rastrear quando um elemento no DOM entra na janela de visualização visível. Isso pode ser útil para carregar imagens lentamente a tempo ou porque você precisa saber se o usuário está realmente vendo um determinado banner de anúncio. Para fazer isso, vincule o evento de rolagem ou use um timer periódico e chame getBoundingClientRect()
nesse elemento.
No entanto, essa abordagem é muito lenta, porque cada chamada para getBoundingClientRect()
força o navegador a redefinir o layout da página inteira e introduzir uma instabilidade considerável no site. Os casos se tornam quase impossíveis quando você sabe que seu site está sendo carregado dentro de um iframe e quer saber quando o usuário consegue visualizar um elemento. O modelo de origem única e o navegador não permitem que você acesse dados da página da Web que contém o iframe. Esse é um problema comum para anúncios, por exemplo, que são frequentemente carregados usando iframes.
O IntersectionObserver
foi criado para tornar esse teste de visibilidade mais eficiente e está disponível em todos os navegadores mais recentes. IntersectionObserver
informa quando um elemento observado entra ou sai da janela de visualização do navegador.
Como criar um IntersectionObserver
A API é bem pequena. Para descrever isso melhor, vamos usar um exemplo:
const io = new IntersectionObserver(entries => {
console.log(entries);
}, {
/* Using default options. Details below */
});
// Start observing an element
io.observe(element);
// Stop observing an element
// io.unobserve(element);
// Disable entire IntersectionObserver
// io.disconnect();
Usando as opções padrão de IntersectionObserver
, o callback será chamado quando o elemento entrar parcialmente na visualização e sair completamente da janela de visualização.
Se você precisa observar vários elementos, é possível observar vários elementos usando a mesma instância de IntersectionObserver
chamando observe()
várias vezes.
Um parâmetro entries
é transmitido ao callback, que é uma matriz de objetos IntersectionObserverEntry
. Cada um desses objetos contém dados de interseção atualizados para um dos elementos observados.
🔽[IntersectionObserverEntry]
time: 3893.92
🔽rootBounds: ClientRect
bottom: 920
height: 1024
left: 0
right: 1024
top: 0
width: 920
🔽boundingClientRect: ClientRect
// ...
🔽intersectionRect: ClientRect
// ...
intersectionRatio: 0.54
🔽target: div#observee
// ...
rootBounds
é o resultado da chamada de getBoundingClientRect()
no elemento raiz, que é a janela de visualização por padrão. boundingClientRect
é o resultado de uma getBoundingClientRect()
chamada no elemento observado. intersectionRect
é a interseção desses dois retângulos e informa qual parte do elemento observado está visível. intersectionRatio
está intimamente relacionado e informa quanto do elemento está visível. Com essas informações à sua disposição, agora é possível implementar recursos como o carregamento just-in-time de recursos antes que eles se tornem visíveis na tela. com eficiência.
IntersectionObserver
s entregam dados de forma assíncrona, e seu código de callback será executado na linha de execução principal. Além disso, a especificação diz que as implementações de IntersectionObserver
precisam usar requestIdleCallback()
. Isso significa que a chamada para o callback fornecido tem baixa prioridade e será feita pelo navegador durante o tempo de inatividade. Essa é uma decisão de design consciente.
Divs de rolagem
Não gosto muito de rolar dentro de um elemento, mas não estou aqui para julgar, nem IntersectionObserver
. O objeto options
usa uma opção root
que permite definir uma alternativa à janela de visualização como raiz. É importante lembrar que root
precisa ser um ancestral de todos os elementos observados.
Cruze todas as coisas!
Não! Que desenvolvedor ruim! Isso não significa o uso consciente dos ciclos de CPU do usuário. Vamos considerar como exemplo um botão de rolagem infinito: nesse cenário, é aconselhável adicionar sentinelas ao DOM e observar e reciclar essas informações. Adicione uma sentinela perto do último item no botão de rolagem infinito. Quando essa sentinela aparece, é possível usar o callback para carregar dados, criar os próximos itens, anexá-los ao DOM e reposicionar a sentinela de maneira adequada. Se você reciclar corretamente a sentinela, nenhuma outra chamada para observe()
é necessária. O IntersectionObserver
continua funcionando.
Quero receber mais atualizações
Como mencionado anteriormente, o callback será acionado uma única vez quando o elemento observado ficar parcialmente visível e em outra vez quando ele sair da janela de visualização. Dessa forma, IntersectionObserver
dará uma resposta à pergunta: "O elemento X está visível?". No entanto, em alguns casos de uso, isso pode não ser suficiente.
É aí que entra a opção threshold
. Ele permite definir uma matriz de limites de intersectionRatio
. O callback será chamado sempre que o método intersectionRatio
ultrapassar um desses valores. O valor padrão de threshold
é [0]
, que explica o comportamento padrão. Se mudarmos threshold
para [0, 0.25, 0.5, 0.75, 1]
, receberemos uma notificação sempre que um quarto adicional do elemento se tornar visível:
Alguma outra opção?
No momento, só há uma opção adicional entre as listadas acima. rootMargin
permite especificar as margens para a raiz, permitindo aumentar ou diminuir a área usada para interseções. Essas margens são especificadas por meio de uma string estilo CSS, á la "10px 20px 30px 40px"
, com as margens superior, direita, inferior e esquerda, respectivamente. Para resumir, o struct de opções IntersectionObserver
oferece as seguintes opções:
new IntersectionObserver(entries => {/* … */}, {
// The root to use for intersection.
// If not provided, use the top-level document's viewport.
root: null,
// Same as margin, can be 1, 2, 3 or 4 components, possibly negative lengths.
// If an explicit root element is specified, components may be percentages of the
// root element size. If no explicit root element is specified, using a
// percentage is an error.
rootMargin: "0px",
// Threshold(s) at which to trigger callback, specified as a ratio, or list of
// ratios, of (visible area / total area) of the observed element (hence all
// entries must be in the range [0, 1]). Callback will be invoked when the
// visible ratio of the observed element crosses a threshold in the list.
threshold: [0],
});
Mágica de <iframe>
As IntersectionObserver
s foram criadas especificamente para os serviços de anúncios e widgets de redes sociais, que usam elementos <iframe>
com frequência e podem se beneficiar de saber se estão visíveis. Se uma <iframe>
observar um dos elementos, rolar a <iframe>
e rolar a janela contendo a <iframe>
vai acionar o callback nos momentos adequados. No entanto, no último caso, rootBounds
será definido como null
para evitar o vazamento de dados entre as origens.
Sobre o que IntersectionObserver
não?
É preciso lembrar que IntersectionObserver
intencionalmente não é intencionalmente perfeito para pixels nem baixa latência. O uso delas para implementar esforços como animações dependentes de rolagem provavelmente falhará, já que os dados estarão desatualizados quando você começar a usá-los. A explicação tem mais detalhes sobre os casos de uso originais do IntersectionObserver
.
Quanto trabalho posso fazer na chamada de retorno?
Short 'n Sweet: passar muito tempo no callback fará com que seu app fique atrasado — todas as práticas comuns se aplicam.
Vá em frente e cruze os teus elementos
A IntersectionObserver
é compatível com navegadores, já que está disponível em todos os navegadores mais recentes. Se necessário, um polyfill pode ser usado em navegadores mais antigos e está disponível no repositório da WICG (link em inglês). Obviamente, você não terá os benefícios de desempenho que uma implementação nativa teria com o polyfill usado.
Você pode começar a usar o IntersectionObserver
agora mesmo. Conte o que você achou.