Os IntersectionObservers informam quando um elemento observado entra ou sai da janela de visualização do navegador.
Digamos que você queira acompanhar quando um elemento no DOM entra na janela de visualização visível. Isso pode ser útil para fazer o lazy loading de imagens no momento certo ou para saber se o usuário está realmente olhando para um determinado banner de anúncio. Para isso, conecte 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 refazer o layout de toda a página e introduz uma instabilidade considerável no site. As coisas ficam quase impossíveis quando você sabe que seu 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 carregados com frequência 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 e pode ser descrita melhor com 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 para IntersectionObserver, o callback será chamado quando o elemento aparecer parcialmente e quando ele sair completamente da janela de visualização.
Se você precisar observar vários elementos, é possível e recomendado fazer isso 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 getBoundingClientRect() chamado 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 você pode implementar recursos como o carregamento de recursos just-in-time antes que eles fiquem visíveis na tela. Com eficiência.
Os IntersectionObservers fornecem os dados de forma assíncrona, e o código de callback é 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, e o IntersectionObserver também não. 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.
Interseccione tudo!
Não! Desenvolvedor ruim! Isso não é um uso consciente dos ciclos de CPU do usuário. Vamos pensar em um rolador infinito como exemplo: nesse cenário, é recomendável adicionar sentinelas ao DOM e observar (e reciclar!) esses elementos. Adicione um marcador próximo ao último item no rolador infinito. Quando esse marcador aparece, você pode usar o callback para carregar dados, criar os próximos itens, anexá-los ao DOM e reposicionar o marcador de acordo. Se você reciclar corretamente o sentinela, não será necessário fazer outra chamada para observe(). O IntersectionObserver continua funcionando.
Mais atualizações, por favor
Como mencionado anteriormente, o callback será acionado uma vez quando o elemento observado aparecer parcialmente e outra quando ele sair da janela de visualização. Dessa forma, o IntersectionObserver responde à 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. Seu callback será chamado sempre que intersectionRatio cruzar um desses valores. O valor padrão de threshold é [0], o que explica o comportamento padrão. Se mudarmos threshold para [0, 0.25, 0.5, 0.75, 1], vamos receber uma notificação sempre que mais um quarto do elemento ficar visível:
Há outras opções?
No momento, há apenas uma opção além das listadas acima. rootMargin permite especificar as margens da raiz, o que possibilita aumentar ou diminuir a área usada para interseções. Essas margens são especificadas usando uma string de estilo CSS, como "10px 20px 30px 40px", que especifica as margens superior, direita, inferior e esquerda, respectivamente. Em resumo, a 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],
});
<iframe> de mágica
Os IntersectionObservers foram projetados especificamente para serviços de publicidade e widgets de redes sociais, que frequentemente usam elementos <iframe> e podem se beneficiar de saber se estão visíveis. Se um <iframe> observar um dos elementos, a rolagem do <iframe> e da janela que contém o <iframe> vai acionar o callback nos momentos apropriados. No entanto, no último caso, rootBounds será definido como null para evitar o vazamento de dados entre origens.
O que IntersectionObserver não é?
Vale lembrar que o IntersectionObserver não é perfeito em termos de pixels nem tem baixa latência. Usá-los para implementar ações como animações dependentes de rolagem não vai funcionar, porque os dados estarão, a rigor, desatualizados quando você for usá-los. O documento explicativo tem mais detalhes sobre os casos de uso originais de IntersectionObserver.
Quanto trabalho posso fazer no callback?
Curto e direto: passar muito tempo no callback vai fazer seu app ficar lento. Todas as práticas comuns se aplicam.
Vá em frente e faça a interseção dos elementos
O suporte do navegador para IntersectionObserver é bom, já que ele 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 do WICG. Obviamente, você não terá os benefícios de desempenho que uma implementação nativa oferece ao usar esse polyfill.
Você já pode começar a usar o IntersectionObserver. Conte o que você descobriu.