O Intersection Observer v2 adiciona a capacidade de não apenas observar interseções em si, mas também detectar se o elemento de interseção estava visível no momento da interseção.
A Intersection Observer v1 é uma das APIs que provavelmente são universalmente adoradas. Agora que o
Safari também oferece suporte a ela,
ela finalmente pode ser usada em todos os principais navegadores. Para relembrar rapidamente a API,
recomendo assistir o
Supercharged Microtip de Surma no
Intersection Observer v1 incorporado abaixo.
Você também pode ler o artigo detalhado de Surma.
As pessoas usaram o Intersection Observer v1 para uma ampla gama de casos de uso, como
carregamento lento de imagens e vídeos,
notificações quando os elementos atingem position: sticky
,
eventos de análise acionados
e muito mais.
Para conferir todos os detalhes, consulte a documentação do Intersection Observer no MDN. Mas, como lembrete, a API Intersection Observer v1 tem a seguinte aparência no caso mais básico:
const onIntersection = (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
console.log(entry);
}
}
};
const observer = new IntersectionObserver(onIntersection);
observer.observe(document.querySelector('#some-target'));
Qual é o desafio com o Intersection Observer v1?
Para deixar claro, o Intersection Observer v1 é ótimo, mas não é perfeito. Há
alguns casos em que a API não funciona. Vamos dar uma olhada mais de perto.
A API Intersection Observer v1 pode informar quando um elemento é rolado para a
viewport da janela, mas não informa se o elemento está coberto
por qualquer outro conteúdo da página (ou seja, quando o elemento está obstruído) ou se
a exibição visual do elemento foi modificada por efeitos visuais, como transform
, opacity
, filter
etc., que efetivamente podem torná-lo invisível.
Para um elemento no documento de nível superior, essas informações podem ser determinadas analisando
o DOM por JavaScript, por exemplo, usando
DocumentOrShadowRoot.elementFromPoint()
e depois aprofundando.
Por outro lado, não é possível acessar as mesmas informações se o elemento em questão estiver
localizado em um iframe de terceiros.
Por que a visibilidade real é tão importante?
Infelizmente, a Internet é um lugar que atrai usuários de má-fé com intenções ainda piores.
Por exemplo, um editor duvidoso que veicula anúncios PPC em um site de conteúdo pode ser incentivado
a enganar as pessoas para que cliquem nos anúncios e aumentem o pagamento do editor (pelo menos
por um curto período, até que a rede de publicidade os detecte).
Normalmente, esses anúncios são veiculados em iframes.
Agora, se o editor quisesse fazer com que os usuários clicassem nesses anúncios, ele poderia tornar os iframes de anúncios
completamente transparentes aplicando uma regra CSS iframe { opacity: 0; }
e sobrepondo os iframes
em cima de algo atraente, como um vídeo de gato fofo que os usuários realmente gostariam de clicar.
Isso é chamado de clickjacking.
É possível conferir um ataque de clickjacking na seção de cima desta
demonstração (tente "assistir" o vídeo do gato
e ativar o "modo truque").
Você vai notar que o anúncio no iframe "acha" que recebeu cliques legítimos, mesmo que ele estivesse
completamente transparente quando você clicou nele (fingindo ser involuntário).
Como o Intersection Observer v2 corrige isso?
O Intersection Observer v2 introduz o conceito de rastreamento da "visibilidade" real de um elemento
alvo, como um ser humano o definiria.
Ao definir uma opção no
construtor IntersectionObserver
,
as instâncias
IntersectionObserverEntry
que se cruzam vão conter um novo campo booleano chamado isVisible
.
Um valor true
para isVisible
é uma garantia forte da implementação
de que o elemento de destino não é totalmente ocultado por outro conteúdo
e não tem efeitos visuais aplicados que alterem ou distorçam a exibição na tela.
Por outro lado, um valor false
significa que a implementação não pode fazer essa garantia.
Um detalhe importante da
especificação
é que a implementação é permitida para informar falsas negativas, ou seja, definir isVisible
como false
, mesmo quando o elemento de destino está completamente visível e não modificado.
Por motivos de desempenho ou outros, os navegadores se limitam a trabalhar com caixas
limitantes e geometria retilínea. Eles não tentam alcançar resultados perfeitos para
modificações como border-radius
.
No entanto, falsas correspondências não são permitidas em nenhuma circunstância. Isso significa que
isVisible
não pode ser definido como true
quando o elemento de destino não está completamente visível e não foi modificado.
Como é o novo código na prática?
O construtor IntersectionObserver
agora usa duas propriedades de configuração adicionais: delay
e trackVisibility
.
O delay
é um número que indica o atraso mínimo em milissegundos entre as notificações do
observador para um determinado destino.
O trackVisibility
é um valor booleano que indica se o observador vai rastrear as mudanças na visibilidade
de um destino.
É importante observar que, quando trackVisibility
é true
, delay
precisa ser,
pelo menos, 100
(ou seja, não mais de uma notificação a cada 100 ms).
Como observado anteriormente, a visibilidade é cara para calcular, e esse requisito é uma precaução contra
a degradação de desempenho e o consumo de bateria. O desenvolvedor responsável vai usar o
maior valor tolerável para atraso.
De acordo com a especificação atual, a visibilidade é calculada da seguinte maneira:
Se o atributo
trackVisibility
do observador forfalse
, o destino será considerado visível. Isso corresponde ao comportamento atual da v1.Se o alvo tiver uma matriz de transformação efetiva diferente de uma tradução 2D ou aumento de escala 2D proporcional, ele será considerado invisível.
Se o destino ou qualquer elemento na cadeia de blocos que o contém tiver uma opacidade efetiva diferente de 1,0, ele será considerado invisível.
Se o destino ou qualquer elemento na cadeia de blocos que o contém tiver filtros aplicados, ele será considerado invisível.
Se a implementação não puder garantir que o alvo não seja completamente oculto por outro conteúdo da página, ele será considerado invisível.
Isso significa que as implementações atuais são bastante conservadoras ao garantir a visibilidade.
Por exemplo, aplicar um filtro de escala de cinza quase imperceptível, como filter: grayscale(0.01%)
,
ou definir uma transparência quase invisível com opacity: 0.99
renderizaria o elemento
invisível.
Confira abaixo um exemplo de código curto que ilustra os novos recursos da API. Você pode conferir a lógica de rastreamento de cliques em ação na segunda seção da demonstração (mas agora, tente "assistir" o vídeo do filhote). Ative o "modo de truque" novamente para se tornar imediatamente um editor duvidoso e conferir como o Intersection Observer v2 impede que cliques em anúncios não legítimos sejam rastreados. Desta vez, o Intersection Observer v2 está aqui para ajudar. 🎉
<!DOCTYPE html>
<!-- This is the ad running in the iframe -->
<button id="callToActionButton">Buy now!</button>
// This is code running in the iframe.
// The iframe must be visible for at least 800ms prior to an input event
// for the input event to be considered valid.
const minimumVisibleDuration = 800;
// Keep track of when the button transitioned to a visible state.
let visibleSince = 0;
const button = document.querySelector('#callToActionButton');
button.addEventListener('click', (event) => {
if ((visibleSince > 0) &&
(performance.now() - visibleSince >= minimumVisibleDuration)) {
trackAdClick();
} else {
rejectAdClick();
}
});
const observer = new IntersectionObserver((changes) => {
for (const change of changes) {
// ⚠️ Feature detection
if (typeof change.isVisible === 'undefined') {
// The browser doesn't support Intersection Observer v2, falling back to v1 behavior.
change.isVisible = true;
}
if (change.isIntersecting && change.isVisible) {
visibleSince = change.time;
} else {
visibleSince = 0;
}
}
}, {
threshold: [1.0],
// 🆕 Track the actual visibility of the element
trackVisibility: true,
// 🆕 Set a minimum delay between notifications
delay: 100
}));
// Require that the entire iframe be visible.
observer.observe(document.querySelector('#ad'));
Links relacionados
- Rascunho mais recente do editor da especificação do Intersection Observer.
- Intersection Observer v2 no Status da plataforma do Chrome.
- Intersection Observer v2 bug do Chromium.
- Blink Intent to Implement posting.
Agradecimentos
Agradecemos a Simeon Vincent, Yoav Weiss e Mathias Bynens por revisar este artigo, bem como a Stefan Zager por revisar e implementar o recurso no Chrome. Imagem principal de Sergey Semin no Unsplash.