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 do DOM entra na janela de visualização visível. É recomendável fazer isso para que você possa carregar imagens lentamente no momento certo ou porque precisa saber se o usuário está realmente olhando para um determinado banner de anúncio. Para fazer isso, ative o evento de rolagem ou use um timer periódico e chame getBoundingClientRect()
nesse elemento.
No entanto, essa abordagem é muito lenta, já que cada chamada para getBoundingClientRect()
força o navegador a redefinir o layout da página inteira e introduz uma instabilidade considerável no site. Os casos são quase impossíveis quando você sabe que o site está sendo carregado dentro de um iframe e quer saber quando o usuário pode ver 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 projetado para tornar esse teste de visibilidade mais eficiente, e ele 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 e é melhor descrita usando 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();
Com as opções padrão de IntersectionObserver
, seu callback será chamado quando o elemento entrar parcialmente e sair completamente da janela de visualização.
Se você precisa observar vários elementos, é possível e recomendado observar vários elementos usando a mesma instância de IntersectionObserver
chamando observe()
várias vezes.
Um parâmetro entries
é transmitido para o callback, que é uma matriz de objetos IntersectionObserverEntry
. Cada um desses objetos contém dados atualizados de interseção 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 de chamar getBoundingClientRect()
no elemento raiz, que é a janela de visualização por padrão. boundingClientRect
é o resultado de getBoundingClientRect()
chamado no elemento observado. intersectionRect
é a interseção desses dois retângulos e informa efetivamente qual parte do elemento observado está visível. intersectionRatio
está diretamente 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 retorno de chamada 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 sou muito fã 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 ter em mente que root
precisa ser um ancestral de todos os elementos observados.
Cruze todas as coisas!
Não! Desenvolvedor ruim! Isso não representa o uso consciente dos ciclos de CPU do usuário. Vamos considerar um exemplo de rolagem infinita: nesse cenário, é definitivamente aconselhável adicionar sentinelas ao DOM e observar e reciclar essas informações. Adicione uma sentinela próxima ao último item no botão de rolagem infinito. Quando essa sentinela aparece, você pode usar o callback para carregar dados, criar os próximos itens, anexá-los ao DOM e reposicionar a sentinela de acordo. Se você reciclar corretamente a sentinela, não é necessária nenhuma chamada adicional para observe()
. O IntersectionObserver
continua funcionando.

Mais atualizações
Como mencionado anteriormente, o callback será acionado uma única vez quando o elemento observado entrar parcialmente na visualização e em outra vez quando ele sair da janela de visualização. Dessa forma, IntersectionObserver
dá uma resposta à pergunta "O elemento X está visível?". No entanto, em alguns casos, isso pode não ser suficiente.
É aí que entra a opção threshold
. Ela permite definir uma matriz de limites de intersectionRatio
. O callback será chamado sempre que 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]
, seremos notificados sempre que um quarto adicional do elemento se tornar visível:

Alguma outra opção?
No momento, só há uma opção adicional além das listadas acima. rootMargin
permite especificar as margens para a raiz, permitindo aumentar ou reduzir a área usada para interseções. Essas margens são especificadas por uma string estilo CSS, á la "10px 20px 30px 40px"
, especificando as margens superior, direita, inferior e esquerda, respectivamente. Para resumir, o struct 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>
Os IntersectionObserver
s foram criados especificamente pensando nos serviços de anúncios e widgets de rede social, 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, tanto a rolagem da <iframe>
quanto a rolagem da janela contendo o <iframe>
acionarão o callback nos momentos adequados. No entanto, no último caso, rootBounds
será definido como null
para evitar o vazamento de dados entre as origens.
Do que se trata a IntersectionObserver
Não?
Tenha em mente que IntersectionObserver
intencionalmente não é pixel perfeito nem baixa latência. É comum usá-los para implementar iniciativas como animações dependentes de rolagem, já que os dados estarão desatualizados quando você for usá-los. O explicador tem mais detalhes sobre os casos de uso originais de IntersectionObserver
.
Quanto trabalho posso fazer na chamada de retorno?
Short 'n Sweet: passar muito tempo no callback deixará seu app atrasado. Todas as práticas comuns se aplicam.
Vá em frente e cruze os teus elementos
O suporte ao IntersectionObserver
é bom, já que está disponível em todos os navegadores modernos. Se necessário, um polyfill pode ser usado em navegadores mais antigos e está disponível no repositório do WICG (link em inglês). Obviamente, você não terá os benefícios de desempenho que uma implementação nativa ofereceria usando esse polyfill.
Você já pode começar a usar o IntersectionObserver
! Conte o que você teve.