Observando elementos visibles

Observadores con IntersectionObserver y MutationObserver


En situaciones especiales podríamos tener una sección de nuestra página donde necesitamos utilizar muchos eventos en elementos que desaparecen y aparecen frecuentemente (sección muy dinámica), o situaciones similares.

En este artículo vamos a explorar estas direcciones:

  • 1️⃣ Usamos IntersectionObserver para detectar cuando un elemento entra/sale de la vista del usuario.
  • 2️⃣ Usamos MutationObserver para detectar cambios en los elementos de una parte del DOM.

Usando IntersectionObserver

La API de IntersectionObserver nos permite estar vigilando u observando elementos de la página, y detectar cuando un elemento entra o sale del viewport (región visible) del navegador. De esta forma, puedes saber si un elemento está visible o no.

Observa este ejemplo, donde creamos un IntersectionObserver:

  • 1️⃣ El parámetro entries es un de IntersectionObserverEntry.
  • 2️⃣ Cada elemento del array contiene al menos las propiedades target y isIntersecting.
  • 3️⃣ La propiedad target tiene el elemento que ha entrado/salido del viewport.
  • 4️⃣ La propiedad isIntersecting es un que indica si está en el viewport o no.
  • 5️⃣ Si el valor es true, ha entrado al viewport. Si el valor es false, ha salido del viewport.
const action = () => alert("¡Botón clickeado!");

const intersectionObserver = new IntersectionObserver(entries => {
  entries.forEach(({ target, isIntersecting }) => {
    if(isIntersecting) {
      target.addEventListener("click", action);
    } else {
      target.removeEventListener("click", action);
    }
  });
}, { threshold: 0.5 });

El punto clave de este ejemplo es que si el elemento aparece en la región visible de la página, hará un addEventListener() y le añadirá el evento, pero si desaparece de la región visible, entonces hará un removeEventListener() y lo eliminará.

En las opciones, establecemos la opción threshold a 0.5. Este valor de 0 a 1 indica la presición con la que se detectará si el elemento está en el viewport.

Usando MutationObserver

La API MutationObserver nos permite detectar cambios en el DOM, y ejecutar una función cuando se producen cambios. De esta forma, es mucho más sencillo detectar cuando se añaden o eliminan elementos fácilmente.

Observa este ejemplo, donde creamos un MutationObserver:

  • 1️⃣ El parámetro mutations es un de MutationRecord.
  • 2️⃣ Cada elemento del array contiene al menos los addedNodes y removedNodes.
  • 3️⃣ La propiedad addedNodes contiene los nodos que se añadieron en la zona observada.
  • 4️⃣ La propiedad removedNodes contiene los nodos que se eliminaron de la zona observada.
  • 5️⃣ Si alguno de los nodos implicados tiene la clase observed, se añade/elimina su observación con IntersectionObserver.
const mutationObserver = new MutationObserver(mutations => {
  mutations.forEach(mutation => {
    mutation.addedNodes.forEach(node => {
      if (node.matches?.('.observed')) {
        intersectionObserver.observe(node);
      }
    });
    mutation.removedNodes.forEach(node => {
      if (node.matches?.('.observed')) {
        intersectionObserver.unobserve(node);
      }
    });
  });
});

const container = document.querySelector(".container");
mutationObserver.observe(container, { childList: true, subtree: true });

En las últimas lineas, simplemente localizamos el elemento .container y vigilamos los nodos que se añaden o eliminan de esa zona del DOM con MutationObserver.

Ejemplo completo

Finalmente, veamos el ejemplo completo donde mezclamos los ejemplos anteriores IntersectionObserver y MutationObserver simplificados:

const action = () => alert("¡Botón clickeado!");

const intersectionObserver = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    const methodName = entry.isIntersecting ? "addEventListener" : "removeEventListener";
    entry.target[methodName]("click", action);
  });
}, { threshold: 0.5 });

const mutationObserver = new MutationObserver(mutations => {
  mutations.forEach(mutation => {
    mutation.addedNodes.forEach(node => {
      const isObserved = node.matches?.('.observed');
      isObserved && intersectionObserver.observe(node);
    });
    mutation.removedNodes.forEach(node => {
      const isObserved = node.matches?.('.observed');
      isObserved && intersectionObserver.unobserve(node);
    });
  });
});

const container = document.querySelector(".container");
mutationObserver.observe(container, { childList: true, subtree: true });

Con este ejemplo, estaremos vigilando cuando se añaden o eliminan elementos de una zona del DOM, y a dichos elementos podremos añadir sus respectivos listeners sólo cuando se encuentre dentro del viewport. Si los elementos no están en la zona del viewport, se retiran sus listeners y no se gastan recursos en tenerlos activos.

Obviamente, esto es una estrategia muy particular que habría que analizar si es necesaria para nuestro caso de uso. Recuerda que estrategias como Delegación de eventos y AbortController suelen ser más simples y más que suficientes en la mayoría de los casos.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev