Propagación de eventos

Fases de los eventos: Capture y Bubbling


Hasta ahora hemos visto como trabajar con eventos directamente. Sin embargo, nos hemos saltado una parte fundamental del tema de eventos, que es la propagación de eventos en el DOM.

Recapitulemos. Hasta ahora hemos visto que los eventos son objetos que se disparan en el DOM para informar de un suceso, generalmente realizado por el usuario (aunque no tiene que ser necesariamente). Si no profundizamos en el tema, podríamos pensar que los eventos aparecen mágicamente en un elemento cuando ocurren, pero esto no sucede así realmente.

¿Cómo se propagan los eventos?

Los eventos no aparecen magicamente en el DOM, sino que se generan desde el elemento raíz del DOM y se van moviendo por todos los elementos hasta llegar al más profundo, donde se originó el evento. Una vez ahí, vuelve a recorrer el DOM en sentido inverso hasta volver al elemento raíz:

Esto ocurre de forma transparente al usuario y al desarrollador, que si no está escuchando el evento en ninguno de esos elementos, no ocurrirá nada. Este proceso se llama propagación de eventos y es la forma natural de que los elementos de una página tengan conocimiento de que ha ocurrido un cierto suceso y nos permite que cualquier elemento de la página pueda responder a ese evento.

Aunque hemos hablado de elementos HTML concretamente, en Javascript también se puede escuchar el elemento document, que hace referencia a la página en la que estamos y estaría antes de <html>.

Fases de la propagación

Como hemos visto anteriormente, hay dos «vueltas» de propagación:

  • 1️⃣ La primera, la fase de captura que va desde la raíz hasta el elemento más profundo.
  • 2️⃣ La segunda, la fase de burbujeo que va desde el elemento más profundo hasta la raíz.

Propagación de eventos: Capture vs Bubbles

Ten en cuenta que si hicieramos click en el elemento .child, el evento se propagaría desde el raíz hasta el elemento .child y se detendría ahí. El elemento .baby nunca se enteraría del evento.

En nuestro código, es posible que queramos detectar eventos en diferentes fases de propagación. Normalmente, en la mayoría de los casos, los eventos se capturan siguiendo la modalidad target, es decir, escuchando los elementos objetivos. Sin embargo, en ciertas situaciones nos puede interesar saber el orden de procesamiento de eventos. En esos casos nos puede interesar alternar entre el modo capture y el modo bubbling.

Veamos un poco de código para ver como trabajar en cada fase.

Cuando hablamos de la fase capture nos referimos a que estamos capturando los eventos en la primera fase de la propagación, por lo que los elementos irán en el orden de raíz a elemento profundo. La forma de indicar esta fase se hace con el objeto de opciones del addEventListener() con el capture a true.

const parent = document.querySelector(".parent");
const child = document.querySelector(".child");
const baby = document.querySelector(".baby")

const add = (text) => {
  baby.textContent += ` ${text} -> `;
}

parent.addEventListener("click", (event) => add("Parent"), { capture: true });
child.addEventListener("click", (event) => add("Child"), { capture: true });
baby.addEventListener("click", (event) => add("Baby"), { capture: true });
<div class="parent">
  <div class="child">
    <div class="baby">
      Click here
    </div>
  </div>
</div>
div {
  padding: 2rem;
  background: var(--color);
}

.parent { --color: #1864ab }
.child { --color: #339af0 }
.baby { --color: #a5d8ff }

Observa que al hacer click en la demo, nos aparece que el orden de recibir y procesar los eventos con addEventListener() es primero el elemento más cercano a la raíz y luego los elementos cada vez más lejanos (y profundos en el DOM).

Aunque las fases de la propagación son capture y bubbling, en muchos casos nos encontraremos que se hace referencia a target como una fase. Cuando se menciona esto, se refiere a que estamos simplemente haciendo referencia al elemento objetivo (target) que nos interesa, por lo que aquí no tiene sentido saber el orden.

Simplemente nos enfocamos en usar el evento en el elemento en cuestión:

const parent = document.querySelector(".parent");
const child = document.querySelector(".child");
const baby = document.querySelector(".baby")

const add = (text) => {
  baby.textContent += ` ${text} -> `;
}

baby.addEventListener("click", (event) => add("Baby"));
<div class="parent">
  <div class="child">
    <div class="baby">
      Click here
    </div>
  </div>
</div>
div {
  padding: 2rem;
  background: var(--color);
}

.parent { --color: #1864ab }
.child { --color: #339af0 }
.baby { --color: #a5d8ff }

Por otro lado, la segunda fase de la propagación de eventos es la fase denominada bubbling. Es la fase por defecto que se utiliza, y actúa de esta forma cuando no tenemos el objeto de opciones con un capture a true.

Recuerda que en esta fase, estamos volviendo de la propagación, o sea, desde el elemento más profundo al elemento raíz:

const parent = document.querySelector(".parent");
const child = document.querySelector(".child");
const baby = document.querySelector(".baby")

const add = (text) => {
  baby.textContent += ` ${text} -> `;
}

parent.addEventListener("click", (event) => add("Parent"));
child.addEventListener("click", (event) => add("Child"));
baby.addEventListener("click", (event) => add("Baby"));
<div class="parent">
  <div class="child">
    <div class="baby">
      Click here
    </div>
  </div>
</div>
div {
  padding: 2rem;
  background: var(--color);
}

.parent { --color: #1864ab }
.child { --color: #339af0 }
.baby { --color: #a5d8ff }

En este caso, al hacer click, veremos que el orden es desde el elemento más profundo, en nuestro caso .baby al elemento más cerca de la raíz, en nuestro caso .parent.

¿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