// apis · Web Platform Advent #10
A API IntersectionObserver: lazy-loading e efeitos de rolagem
Use o IntersectionObserver para carregar imagens em lazy-loading, revelar elementos ao rolar e criar rolagem infinita — com threshold e rootMargin explicados, além de exemplos executáveis.
A API IntersectionObserver informa quando um elemento entra ou sai da viewport — sem que você precise ligar um listener de scroll e calcular posições a cada frame. O navegador faz a observação de forma assíncrona e fora da thread principal, então tudo permanece fluido mesmo com centenas de alvos. Todos os navegadores atuais a suportam.
Por que não simplesmente ouvir o scroll?
Um handler de scroll dispara constantemente e te obriga a chamar getBoundingClientRect() para saber onde as coisas estão — isso lê o layout e pode causar travamentos. O IntersectionObserver inverte o modelo: você registra os elementos que lhe interessam e o navegador te notifica apenas quando a visibilidade deles realmente muda.
O padrão básico
Crie um observer com um callback e depois chame observe() em um ou mais elementos. O callback recebe um array de objetos entry; a propriedade chave é entry.isIntersecting:
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
console.log('Now visible:', entry.target);
}
}
});
document.querySelectorAll('.watch').forEach((el) => observer.observe(el)); Lazy-loading de imagens
Um uso clássico: carregar uma imagem apenas quando ela está prestes a entrar na visão. Armazene a URL real em data-src, troque-a quando o elemento interseccionar e então pare de observá-lo:
const lazyImages = document.querySelectorAll('img[data-src]');
const imgObserver = new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); // load once, then stop watching
}
}, { rootMargin: '200px' });
lazyImages.forEach((img) => imgObserver.observe(img)); Observação: o lazy-loading nativo via <img loading="lazy"> hoje cobre a maioria dos casos. Recorra ao IntersectionObserver quando precisar de lógica personalizada — placeholders, fade-ins ou o carregamento de recursos que não sejam imagens.
Revelar elementos ao rolar
Adicione uma classe CSS quando um elemento se torna visível pela primeira vez, para que ele possa animar a entrada. Use a opção threshold para decidir quanto do elemento precisa estar visível antes que o callback dispare:
const revealObserver = new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
}
}, { threshold: 0.2 }); // fire when 20% is on screen
document.querySelectorAll('.reveal').forEach((el) => revealObserver.observe(el)); Rolagem infinita
Coloque um elemento sentinela vazio depois da sua lista. Quando ele entra na viewport, busque e anexe a próxima página, e continue observando para a página seguinte:
const sentinel = document.querySelector('#sentinel');
let page = 1;
const loader = new IntersectionObserver(async (entries) => {
if (!entries[0].isIntersecting) return;
const items = await fetchPage(page++);
appendItems(items);
});
loader.observe(sentinel); As opções: threshold e rootMargin
Duas opções determinam quando o callback dispara:
threshold— um número (ou array) de 0 a 1.0dispara no momento em que um pixel está visível;1dispara apenas quando o elemento inteiro está visível. Um array como[0, 0.5, 1]dispara a cada passo, útil para efeitos de progresso de rolagem.rootMargin— uma margem no estilo CSS que aumenta ou reduz a área usada no teste.'200px'aciona 200px antes de o elemento alcançar a viewport — ideal para pré-carregamento.root— o elemento a usar como viewport. Por padrão é a viewport do navegador; defina-o como um contêiner rolável para observar dentro dele.
Limpeza
Pare de observar um único elemento com observer.unobserve(el), ou desmonte tudo com observer.disconnect() — importante em single-page apps quando uma view é desmontada, para evitar vazamentos.
Referência rápida
| Objetivo | Opção / chamada chave |
|---|---|
| Detectar visibilidade | entry.isIntersecting |
| Pré-carregar cedo | rootMargin: '200px' |
| Esperar até X% visível | threshold: 0.5 |
| Observar dentro de um contêiner | root: containerEl |
| Parar de observar um | observer.unobserve(el) |
| Desmontar tudo | observer.disconnect() |
O IntersectionObserver substitui toda uma categoria de cálculos frágeis de rolagem por uma API pequena e declarativa. Imagens lazy, revelações ao rolar e listas infinitas reduzem-se todas ao mesmo padrão: observar, reagir a isIntersecting e parar de observar quando terminar.