Intersection Observer es una de esas APIs que probablemente son muy populares y se pueden usar en todos los navegadores principales. Los desarrolladores usaron esta API para una amplia variedad de casos de uso, como carga diferida de imágenes y videos, notificaciones cuando los elementos alcanzan position: sticky, activación de eventos de Analytics y muchos más.
En su forma más básica, esta es la apariencia de la API de Intersection Observer v1:
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'));
Desafíos de visibilidad en Intersection Observer v1
Con la API de Intersection Observer v1, puedes saber cuándo se desplaza un elemento al viewport de la ventana. Sin embargo, no puedes determinar si ese elemento está cubierto por otro contenido de la página, lo que se conoce como oclusión, o si el elemento aparece modificado por CSS, como transform, opacity o filter, lo que puede hacer que el elemento sea invisible.
En el caso de un elemento del documento de nivel superior, esta información se puede determinar analizando el DOM con JavaScript, por ejemplo, con DocumentOrShadowRoot.elementFromPoint().
En cambio, no se puede obtener la misma información si el elemento en cuestión se encuentra en un iframe de terceros.
¿Por qué es importante la visibilidad?
Lamentablemente, en Internet hay personas o entidades que actúan de mala fe. Por ejemplo, un publicador deshonesto podría usar anuncios de pago por clic en un sitio web. Es posible que engañen a los usuarios para que hagan clic en estos anuncios y ganar más dinero, al menos hasta que la red de publicidad descubra su plan. Por lo general, estos anuncios se publican en iframes.
Para engañar a los usuarios, el editor podría hacer que los iframes de los anuncios sean completamente transparentes con CSS: iframe { opacity: 0; }. Luego, podrían colocar estos iframes transparentes sobre contenido atractivo, como un video de un gato tierno, en el que los usuarios quieran hacer clic.
Esto se denomina clickjacking.
Puedes ver un ataque de clickjacking en acción en la sección superior de nuestra demostración. Intenta "mirar" el video del gato y activa el modo de truco. El anuncio en el iframe registra los clics como legítimos, incluso si hiciste clic en él (sin intención) mientras el iframe era transparente.

Mejoras en Intersection Observer v2
Intersection Observer v2 puede hacer un seguimiento de la "visibilidad" de un elemento tal como la definiría una persona. Si configuras una opción en el constructor IntersectionObserver, las instancias de IntersectionObserverEntry resultantes incluyen un nuevo campo booleano llamado isVisible. Cuando isVisible es true, el navegador se asegura de que el elemento no esté cubierto por otro contenido y no tenga efectos visuales que oculten o cambien su visualización. Si isVisible es false, el navegador no puede brindar esa garantía.
La especificación permite falsos negativos: isVisible puede ser false incluso cuando el elemento es realmente visible y no se modificó. Para mejorar el rendimiento, los navegadores usan cálculos más simples, como cuadros delimitadores y formas rectangulares, y no verifican cada píxel para detectar detalles complejos como border-radius.
Sin embargo, no se permiten los falsos positivos bajo ninguna circunstancia. Esto significa que isVisible no será true si el elemento no está completamente visible y sin modificaciones.
Aplica estos cambios
El constructor IntersectionObserver ahora toma dos propiedades de configuración adicionales:
delayes un número que indica la demora mínima en milisegundos entre las notificaciones del observador para un destino determinado.trackVisibilityes un valor booleano que indica si el observador hará un seguimiento de los cambios en la visibilidad de un objetivo.
Cuando trackVisibility es true, delay debe establecerse en 100 o en un valor superior (es decir, no más de una notificación cada 100 ms).
Como la visibilidad es costosa de calcular, esta es una precaución contra la degradación del rendimiento y el consumo de batería. Los desarrolladores responsables deben usar el valor de demora más grande que se pueda tolerar.
La especificación calcula la visibilidad. Al igual que con la versión 1, cuando el atributo trackVisibility del observador es false, el objetivo se considera visible.
En la versión 2, el objetivo se considera invisible en los siguientes casos:
Tiene una matriz de transformación efectiva, además de una traslación 2D o un aumento proporcional 2D.
El objetivo, o cualquier elemento de su cadena de bloques contenedores, tiene una opacidad efectiva inferior a 1.0.
El destino, o cualquier elemento de su cadena de bloques contenedora, tiene filtros aplicados.
Si la implementación no puede garantizar que el objetivo no esté obstruido por otro contenido de la página.
Esto significa que las implementaciones actuales son bastante conservadoras a la hora de garantizar la visibilidad. Por ejemplo, aplicar un filtro de escala de grises casi imperceptible (filter: grayscale(0.01%)) o establecer la transparencia más pequeña (opacity: 0.99) haría que el elemento fuera invisible.
A continuación, se muestra un ejemplo de código que ilustra las nuevas funciones de la API. Puedes ver la lógica de seguimiento de clics en acción en la segunda sección de la demostración. Intenta "mirar" el video del cachorro. Activa el modo de engaño para convertirte en una persona o entidad que actúa de mala fe y ver cómo Intersection Observer v2 evita que se haga un seguimiento de los clics en anuncios no legítimos. Intersection Observer v2 nos protege.

<!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 v2, fallback 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'));
Recursos adicionales
- Borrador de trabajo de Intersection Observer.
- Intersection Observer v2 en el Estado de la plataforma de Chrome
Agradecimientos
Gracias a Simeon Vincent, Yoav Weiss y Mathias Bynens por sus revisiones, y a Stefan Zager por revisar e implementar la función en Chrome.