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 unde IntersectionObserverEntry
. - 2️⃣ Cada elemento del array contiene al menos las propiedades
target
yisIntersecting
. - 3️⃣ La propiedad
target
tiene el elemento que ha entrado/salido del viewport. - 4️⃣ La propiedad
isIntersecting
es unque indica si está en el viewport o no. - 5️⃣ Si el valor es
true
, ha entrado al viewport. Si el valor esfalse
, 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
a0.5
. Este valor de0
a1
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 unde MutationRecord
. - 2️⃣ Cada elemento del array contiene al menos los
addedNodes
yremovedNodes
. - 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 conIntersectionObserver
.
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.