</> HTML5Advent
ENFRESDEITPT

// 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.

Vista de cima da mesa de um designer com esboços de wireframe de UI, notas adesivas, um smartphone e marcadores coloridos

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.

Um smartphone exibindo a tela de login do Facebook ao lado de peças de Scrabble formando as palavras SOCIAL MEDIA
Feeds de rolagem infinita como os aplicativos de redes sociais são um caso clássico de IntersectionObserver: observe um elemento sentinela perto do fim e carregue mais conteúdo quando ele aparecer.

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. 0 dispara no momento em que um pixel está visível; 1 dispara 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

ObjetivoOpção / chamada chave
Detectar visibilidadeentry.isIntersecting
Pré-carregar cedorootMargin: '200px'
Esperar até X% visívelthreshold: 0.5
Observar dentro de um contêinerroot: containerEl
Parar de observar umobserver.unobserve(el)
Desmontar tudoobserver.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.