IntersectionObserver's aparecendo

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

Compatibilidade com navegadores

  • 51
  • 15
  • 55
  • 12.1

Origem

Digamos que você queira rastrear quando um elemento no DOM entra na janela de visualização visível. Isso pode ser útil para carregar imagens lentamente a tempo ou porque você precisa saber se o usuário está realmente vendo um determinado banner de anúncio. Para fazer isso, vincule 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 redefinir o layout da página inteira e introduzir uma instabilidade considerável no site. Os casos se tornam quase impossíveis quando você sabe que seu site está sendo carregado dentro de um iframe e quer saber quando o usuário consegue visualizar 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 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. Para descrever isso melhor, vamos usar 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 de IntersectionObserver, o callback será chamado quando o elemento entrar parcialmente na visualização e sair completamente da janela de visualização.

Se você precisa observar vários elementos, é possível observar vários elementos 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 uma getBoundingClientRect() chamada 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 é possível implementar recursos como o carregamento just-in-time de recursos antes que eles se tornem visíveis na tela. com eficiência.

Proporção de intersecção.

IntersectionObservers 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 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, nem IntersectionObserver. 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.

Cruze todas as coisas!

Não! Que desenvolvedor ruim! Isso não significa o uso consciente dos ciclos de CPU do usuário. Vamos considerar como exemplo um botão de rolagem infinito: nesse cenário, é aconselhável adicionar sentinelas ao DOM e observar e reciclar essas informações. Adicione uma sentinela perto do último item no botão de rolagem infinito. Quando essa sentinela aparece, é possível usar o callback para carregar dados, criar os próximos itens, anexá-los ao DOM e reposicionar a sentinela de maneira adequada. Se você reciclar corretamente a sentinela, nenhuma outra chamada para observe() é necessária. O IntersectionObserver continua funcionando.

Rolagem infinita

Quero receber mais atualizações

Como mencionado anteriormente, o callback será acionado uma única vez quando o elemento observado ficar parcialmente visível e em outra vez quando ele sair da janela de visualização. Dessa forma, IntersectionObserver dará uma resposta à 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. O callback será chamado sempre que o método 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], receberemos uma notificação sempre que um quarto adicional do elemento se tornar visível:

Animação de limite.

Alguma outra opção?

No momento, só há uma opção adicional entre as listadas acima. rootMargin permite especificar as margens para a raiz, permitindo aumentar ou diminuir a área usada para interseções. Essas margens são especificadas por meio de uma string estilo CSS, á la "10px 20px 30px 40px", com as margens superior, direita, inferior e esquerda, respectivamente. Para resumir, o 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],
});

Mágica de <iframe>

As IntersectionObservers foram criadas especificamente para os serviços de anúncios e widgets de redes sociais, 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, rolar a <iframe> e rolar a janela contendo a <iframe> vai acionar o callback nos momentos adequados. No entanto, no último caso, rootBounds será definido como null para evitar o vazamento de dados entre as origens.

Sobre o que IntersectionObserver não?

É preciso lembrar que IntersectionObserver intencionalmente não é intencionalmente perfeito para pixels nem baixa latência. O uso delas para implementar esforços como animações dependentes de rolagem provavelmente falhará, já que os dados estarão desatualizados quando você começar a usá-los. A explicação tem mais detalhes sobre os casos de uso originais do IntersectionObserver.

Quanto trabalho posso fazer na chamada de retorno?

Short 'n Sweet: passar muito tempo no callback fará com que seu app fique atrasado — todas as práticas comuns se aplicam.

Vá em frente e cruze os teus elementos

A IntersectionObserver é compatível com navegadores, já que 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 da WICG (link em inglês). Obviamente, você não terá os benefícios de desempenho que uma implementação nativa teria com o polyfill usado.

Você pode começar a usar o IntersectionObserver agora mesmo. Conte o que você achou.