Entendiendo Intersection Observer

Tabla de contenidos

Cuando construyes una interfaz, una de las peticiones que no falla es que tenga animaciones y efectos de entrada (y a veces de salida) cuando los elementos entran en pantalla. Obviamente tiene que ser en ese momento para que el usuario pueda verlos porque si no, no estarían aportando nada y habrían perdido su valor.

Calculando la posición en pantalla

Antes, procedíamos seleccionando el nodo del DOM y calculando su posición dentro del documento, escuchando el evento scroll, además de su posición según sus coordenadas, la altura de la ventana y la posición del scroll. Así, podíamos averiguar si en ese momento el elemento era visible o no, y hacíamos lo que fuese necesario, como añadir una clase CSS que le cambiase la apariencia, por ejemplo. Una aproximación más o menos así 👇

const target = document.querySelector('.element')

document.addEventListener('scroll', () => {
  const { top, height } = target.getBoundingClientRect()
	
  if (top < window.innerHeight && top + height > 0) {
    // Do some stuff...
  }
})

Este enfoque, además, abría la puerta al debate sobre si es necesario tratar el evento scroll con throttle o no, y la verdad es que no siempre está claro, depende de la situación. Para una cosa puntual quizá no es necesario, y así evitamos añadir dependencias de librerías externas como lodash o de sumar complejidad al asunto con una implementación propia.

NOTA: Hay eventos que se ejecutan muchas veces en un período de tiempo muy pequeño, como ocurre con scroll, que se dispara un montón de veces mientras se scrollea una página. Para optimizar esta situación y que el callback no se ejecute repetidamente, usamos throttle, que es en esencia, una manera de no disparar la función durante un intervalo de tiempo dado.

Intersection Observer

A día de hoy la manera más óptima de conocer la posición de un elemento dentro de una página y sin tener que lidiar directamente con el scroll, es usar IntersectionObserver.

IntersectionObserver es una API de JavaScript que nos permite observar constantemente un elemento. Así que podremos conocer cuándo entra o sale de un área concreta, tomando como referencia el documento o un contenedor padre. De modo que, si el usuario hace scroll (o se dispara un evento resize) podemos enterarnos si la situación de nuestro elemento ha cambiado tras este evento.

Es útil para detectar si un usuario está viendo cierta sección y reaccionar ante ello, ya sea cargando imágenes, más contenido, poniendo y quitando efectos de entrada y salida y en general poder añadir más interactividad enriqueciendo la experiencia del usuario.

Un caso cualquiera

const callback = (entries, observer) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      // Do something...
    }
  });
}

const options = {}
const observer = new IntersectionObserver(callback, options)
let target = document.querySelector('.element')
observer.observe(target)

Primero podemos definir las opciones para el observador (ahora vacías), luego crear una instancia del observador usando IntersectionObserver, seleccionar el nodo que queremos observar y finalmente indicar al observador quién es el objetivo a observar.

Además del objeto de configuración (opcional), el observador espera recibir una función en la que realizamos las operaciones necesarias en caso de que se cumpla alguna condición. En el ejemplo de arriba, que el elemento esté dentro del área observada.

Cada una de estas entradas es de tipo IntersectionObserverEntry. En el caso más simple podemos comprobar el booleano isIntersecting para realizar alguna acción pero tenemos más información relacionada que puede sernos útil.

IntersectionObserverEntry { 
  time: 149248.54, 
  rootBounds: DOMRect, 
  boundingClientRect: DOMRect, 
  intersectionRect: DOMRect, 
  isIntersecting: true, 
  intersectionRatio: 0.04942339373970346, 
  target: article.main-page-content 
}

Cómo cambiar el momento en el que se dispara la función

const options = {
  root: document.querySelector('#container'),
  rootMargin: '0px',
  threshold: 1.0
}

El objeto de configuración afina el comportamiento del observador y define cuándo se va a disparar la función de callback.

Posibles problemas

A veces ocurre, las primeras veces que usamos IntersectionObserver, que no funciona como esperamos, en ese caso nos puede ayudar comprobar algunas cosas.

  1. Que hayamos indicado correctamente el elemento que queremos usar como visor en root. Ojo con los iframe porque entonces no podremos declararlo como null sino como document para que funcione.
  2. Comprobar los valores indicados para rootMargin y que tengan sentido. Cuidado de no colapsar top con bottom, por ejemplo, porque tendremos comportamientos inesperados.
  3. Si el elemento observado es más grande que el área de observación y threshold tiene valor 1.0 tendrás problemas, porque nunca estará enteramente visible en el área observada.

Múltiples referencias

Observar varias referencias es sencillo, tan sólo hay que iterar la colección de referencias (con un forEach por ejemplo) para decir a cada una de ellas que el observador la observe, eso sí, la función es la misma para todas.

Accesibilidad

Si vas a añadir efectos de entrada y salida en tu página, recuerda que aunque puede ser muy atractivo, no tenemos la certeza de que no molesten a algún usuario, y por eso deberíamos adoptar un enfoque incremental o al menos desactivarlas si el usuario así lo prefiere.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Conclusión

IntersectionObserver es la manera más fácil y óptima de controlar si un elemento está dentro de la parte visible de una página o en cierta área, y aunque al principio pueda costar entrar, merece la pena.

Actualmente los navegadores están trabajando en implementar las novedades de la especificación V2, que mejora la versión actual, que tiene algunos problemas conocidos como que la propiedad isVisible de los IntersectionObserverEntry siempre vale false, así que todo parece indicar que es una API que tendrá continuidad en el futuro.

Recursos

comments powered by Disqus

Si te ha parecido interesante

Tanto si tienes alguna duda o quieres charlar sobre este tema, como si el contenido o nuestros perfiles te parecen interesantes y crees que pdemos hacer algo juntos, no dudes en ponerte en contacto con nosotros a través de twitter o en el email hola@mamutlove.com