IntersectionObserver's aparecendo

Os IntersectionObservers informam quando um elemento observado entra ou sai da janela de visualização do navegador.

Browser Support

  • Chrome: 51.
  • Edge: 15.
  • Firefox: 55.
  • Safari: 12.1.

Source

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.

Visibilidade do iframe

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.

Proporção de interseção.

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.

Rolagem infinita

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:

Animação de limite.

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.