La confianza es buena y la observación es mejor: Intersection Observer v2

Intersection Observer v2 agrega la capacidad no solo de observar las intersecciones per se, sino también de detectar si el elemento de intersección era visible en el momento de la intersección.

Intersection Observer v1 es una de esas APIs que probablemente han sido amadas a nivel universal y, ahora que Safari también lo admite, Finalmente, también se puede usar universalmente en los navegadores más importantes. Para hacer un repaso rápido de la API, Te recomiendo ver los videos de Surma Micropropina supercargada en la intersección Observador v1 que está incorporado a continuación. También puedes leer los textos de artículo. Se usó Intersection Observer v1 para una amplia variedad de casos de uso, como carga diferida de imágenes y videos, recibir notificaciones cuando los elementos llegan a position: sticky, activar eventos de análisis, y mucho más.

Para conocer todos los detalles, consulta la Documentos de Intersection Observer en MDN, pero a modo de recordatorio, así es como se ve la API de Intersection Observer v1 en la caso 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'));

¿Cuál es el desafío de Intersection Observer v1?

Para ser claros, Intersection Observer v1 es excelente, pero no es perfecto. Existen algunos casos excepcionales en los que la API no funciona. Analicemos este tema con más detalle. La API de Intersection Observer v1 puede indicarte cuando un elemento se desplaza en la viewport de la ventana, pero no indica si el elemento está cubierto por cualquier otro contenido de la página (es decir, cuando el elemento está oculto) o si la visualización del elemento se modificó con efectos visuales como transform, opacity, filter, etc., que puede hacerlo invisible de forma efectiva.

Para un elemento del documento de nivel superior, esta información se puede determinar el DOM a través de JavaScript, por ejemplo, a través de DocumentOrShadowRoot.elementFromPoint() y, luego, profundizar. Por el contrario, no se puede obtener la misma información si el elemento en cuestión es ubicadas en un iframe de terceros.

¿Por qué es tan importante la visibilidad real?

Desafortunadamente, Internet es un lugar que atrae a personas que actúan de mala fe con peores intenciones. Por ejemplo, se podría incentivar a un publicador sospechoso que publica anuncios de pago por clic en un sitio de contenido engañar a los usuarios para que hagan clic en sus anuncios y, así, aumentar el pago que recibe el publicador (al menos, por un período breve, hasta que la red de publicidad los detecte). Por lo general, este tipo de anuncios se publican en iframes. Si el editor quisiera que los usuarios hicieran clic en esos anuncios, podría crear Es completamente transparente mediante la aplicación de una regla de CSS iframe { opacity: 0; } y la superposición de los iframes. además de algo atractivo, como el video de un gato lindo en el que los usuarios querrían hacer clic. Esto se denomina clickjacking. Puede ver un ataque de clickjacking en acción en la sección superior de esta demo (intenta "mirar" el video del gato) y activa el "modo truco"). Notará que el anuncio en el iframe "piensa" recibió clics legítimos, incluso si sea completamente transparente cuando haces clic (fingir involuntariamente).

Engañar a un usuario para que haga clic en un anuncio; para ello, le aplica un estilo transparente y superpóngalo sobre algo atractivo.

¿Cómo se soluciona este problema con Intersection Observer v2?

Intersection Observer v2 introduce el concepto de hacer un seguimiento de la "visibilidad" real de un objetivo elemento como lo definiría un ser humano. Al establecer una opción Constructor IntersectionObserver, intersecciones IntersectionObserverEntry las instancias contendrán un nuevo campo booleano llamado isVisible. Un valor de true en isVisible es una sólida garantía de la implementación subyacente que el elemento de destino no está oculto en su totalidad por otro contenido y no tiene efectos visuales aplicados que puedan alterar o distorsionar su visualización en pantalla. Por el contrario, un valor false significa que la implementación no puede garantizarlo.

Un detalle importante del especificación es que la implementación puede informar falsos negativos (es decir, configurar isVisible a false, incluso cuando el elemento de destino sea completamente visible y no esté modificado) Por cuestiones de rendimiento o por otros motivos, los navegadores se limitan a trabajar con límites. cajas y geometría rectilínea; no tratan de lograr resultados de píxeles perfectos modificaciones como border-radius.

Dicho esto, no se permiten los falsos positivos en ninguna circunstancia (es decir, establecer isVisible a true cuando el elemento de destino no es completamente visible ni no está modificado

¿Cómo se ve el nuevo código en la práctica?

El constructor IntersectionObserver ahora toma dos propiedades de configuración adicionales: delay. y trackVisibility. delay es un número que indica el retraso mínimo en milisegundos entre las notificaciones del el observador de un objetivo determinado. El trackVisibility es un valor booleano que indica si el observador hará un seguimiento de los cambios en la capa de destino visibilidad.

Es importante tener en cuenta que, cuando trackVisibility es true, delay debe estar en al menos 100 (es decir, no más de una notificación cada 100 ms) Como se mencionó antes, el cálculo de la visibilidad es costoso, y este requisito es una precaución contra la disminución del rendimiento y el consumo de la batería. El desarrollador responsable utilizará las valor tolerable más alto para el retraso.

Según la actual spec, la visibilidad es calculada de la siguiente manera:

  • Si el atributo trackVisibility del observador es false, el objetivo se considera visible. Esto se corresponde con el comportamiento de la versión 1 actual.

  • Si el destino tiene una matriz de transformación eficaz que no sea una traducción 2D o un aumento proporcional en 2D, el objetivo se considera invisible.

  • Si el objetivo, o cualquier elemento de la cadena de bloques que lo contiene, tiene una opacidad efectiva distinta de 1.0, el objetivo se considera invisible.

  • Si al objetivo, o a cualquier elemento de la cadena de bloques que lo contiene, se aplicaron filtros, el objetivo se considera invisible.

  • Si la implementación no puede garantizar que otra página no oculte por completo el destino contenido, el objetivo se considera invisible.

Esto significa que las implementaciones actuales son bastante conservadoras y garantizan la visibilidad. Por ejemplo, aplicar un filtro de escala de grises casi imperceptible, como filter: grayscale(0.01%) o establecer una transparencia casi invisible con opacity: 0.99 representaría el elemento invisibles.

A continuación, se ofrece una breve muestra de código que ilustra las nuevas funciones de la API. Puedes ver su seguimiento de clics la lógica en acción en la segunda sección de la demostración. (pero ahora, intenta "mirar" el video del cachorro). Asegúrate de activar el "modo truco" de nuevo a inmediatamente conviértase en un publicador sospechoso y vea cómo Intersection Observer v2 evita de clics no legítimos en el anuncio. Esta vez, Intersection Observer v2 está para ayudarte. 🎉

Intersection Observer v2 evita un clic no deseado en un anuncio.

<!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'));

Agradecimientos

Agradecimientos a Simeon Vincent: Yoav Weiss y Mathias Bynens por revisar este artículo, así como también Stefan Zager para revisar e implementar la función en Chrome. Imagen hero de Sergey Semin en Unsplash.