Original article: Event Bubbling in JavaScript – How Event Propagation Works with Examples

Los elementos HTML reciben diferentes tipos de eventos, de clic, a blur, a scroll y así sucesivamente.

Un comportamiento que estos eventos tienen en común es el Evento Bubbling. Explicaré qué significa este comportamiento en este artículo.

También hice una versión en vídeo de este artículo, el cual puedes verlo aquí.

¿Qué es evento bubbling?

Evento Bubbling es un concepto en el DOM (Modelo de Objeto del Documento). Sucede cuando un elemento recibe un evento, y ese evento burbujea (o puedes decir que es transmitido o propagado) a sus elementos, padres y ancestros en el árbol del DOM hasta que llega al elemento raíz.

Este es el comportamiento por defecto de los eventos en los elementos a menos que detengas la propagación, el cual explicaré al final de este artículo.

Miremos un ejemplo así pueda explicar mejor cómo funciona el evento bubbling.

El HTML:

<body>
  <div>
    <span>
      <button>Click me!</button>
    </span>
  </div>
</body>

El CSS:

body {
  padding: 20px;
  background-color: pink;
}

div {
  padding: 20px;
  background-color: green;
  width: max-content;
}

span {
  display: block;
  padding: 20px;
  background-color: blue;
}

El resultado:

image-256

El botón es un hijo del span, el cual a la vez es un hijo del div, y el div es un hijo del body. El árbol del DOM luciría así:

image-263
Árbol del DOM para esta interacción

Cuando haces clic en el botón, puedes verlo como si también hicieras clic al span (el fondo azul). Esto es debido a que el botón es un hijo del span.

Es lo mismo también con el div y el body. Cuando haces clic al botón, es como si también hicieras clic al span, al div y al body porque son los ancestros del botón. Esta es la idea del evento bubbling.

Un evento no se detiene en el elemento directo que lo recibe. El evento burbujea hacia sus ancestros, hasta que llega al elemento raíz.

Así que si el botón recibe un evento clic, por ejemplo, el span, div, y body (hasta HTML, el elemento raíz) respectivamente reciben ese evento:

image-264
Ilustración mostrando cómo funciona el event bubbling

También, si haces clic en la caja azul (span), el botón no recibe el evento clic, ya que no es un pariente o ancestro o el span. Pero, el div, el body y el HTML recibirían el evento.

Lo mismo sucedería si haces clic en el div – el evento se propaga a los elementos body y HTML.

Cómo manejar los Eventos que Burbujean

El comportamiento de "Evento Bubbling" hace posible que puedas manejar un evento en un elemento padre en vez del elemento actual que recibió el evento.

El patrón de manejar un evento en un elemento ancestro se llama Delegación de Eventos. Puedes leer sobre esto aquí.

Creamos algunos eventos, escuchadores y manejadores:

const body = document.getElementsByTagName("body")[0]
const div = document.getElementsByTagName("div")[0]
const span = document.getElementsByTagName("span")[0]
const button = document.getElementsByTagName("button")[0]

body.addEventListener('click', () => {
  console.log("se hizo clic en el body")
})

div.addEventListener('click', () => {
  console.log("se hizo clic en el div")
})

span.addEventListener('click', () => {
  console.log("se hizo clic en el span")
})

button.addEventListener('click', () => {
  console.log("se hizo clic en el button")
})

Aquí, hemos seleccionado a los elementos body, div, span y button del DOM. Luego agregamos los eventos escuchadores click para cada uno de ellos y una función manejadora que imprime "se hizo clic en el body", "se hizo clic en el div", "se hizo clic en el span", y "se hizo clic en el botón", respectivamente.

Lo que sucede en la consola cuando haces clic en el fondo rosa (el cual es el body) es:

se hizo clic en el body

Cuando haces clic en el fondo verde (el cual es el div), la consola muestra:

se hizo clic en el div
se hizo clic en el body

El evento "clic" en el elemento body es disparado, aunque el elemento div era el elemento objetivo en el que se hizo clic porque el evento "clic" burbujeó desde el div al body.

Cuando haces clic en el fondo azul (el cual es el span), la consola muestra:

se hizo clic en el span
se hizo clic en el div
se hizo clic en el body

Y, último, cuando haces clic en el botón, la consola muestra:

se hizo clic en el button
se hizo clic en el span
se hizo clic en el div
se hizo clic en el body

Cómo detener el Evento Bubbling

El Evento Bubbling es un comportamiento por defecto para los eventos. Pero en algunos casos, podrías querer evitarlo.

Digamos, por ejemplo, desde nuestro código HTML, que quieres que el div abra un modal cuando se le haga clic. Para el botón, por otro lado, quieres que haga una petición API cuando se le haga clic.

En este caso, no quisieras que la modal abra cuando hagas clic en el botón. Quisieras que el modal solamente abra cuando en realidad le hagas clic (y no cuando hagas clic en cualquier de sus hijos). Aquí es donde la prevención de propagación de eventos entra.

Para evitar el evento bubbling, usas el método stopPropagation del objeto evento.

Cuando se maneja eventos, un objeto evento se pasa a la función manejadora:

button.addEventListener("click", (event) => {
  // hacer lo que sea con el objeto evento
}

El objeto evento contiene las propiedades que tiene información sobre el evento que fue disparado y sobre el elemento en el que fue disparado. Este objeto también contiene métodos – uno de los cuales es stopPropagation().

El método stopPropagation de un evento evita al evento que se propague a sus parientes y ancestros del elemento en el que el evento fue disparado.

Podemos usar esto en el código de JavaScript de arriba:

body.addEventListener('click', () => {
  console.log("se hizo clic en el body")
})

div.addEventListener('click', () => {
  console.log("se hizo clic en el div")
})

span.addEventListener('click', () => {
  console.log("se hizo clic en el span")
})

button.addEventListener('click', (event) => {
  event.stopPropagation()
  console.log("se hizo clic en el button")
})

Con esto, cuando haces clic en el botón, todo lo que obtienes en la consola es:

se hizo clic en el button

Los padres y ancestros del botón no reciben el evento clic, ya que no burbujea desde el botón.

También puedes detener el burbujeo desde un elemento diferente así:

body.addEventListener('click', () => {
  console.log("se hizo clic en el body")
})

div.addEventListener('click', () => {
  console.log("se hizo clic en el div")
})

span.addEventListener('click', (event) => {
  event.stopPropagation()
  console.log("se hizo clic en el span")
})

button.addEventListener('click', () => {
  console.log("se hizo clic en el button")
})

Con el stopPropagation() llamado en el evento oyente del span, y el botón en el que se hizo clic, en la consola obtienes:

se hizo clic en el button
se hizo clic en el span

El evento burbujea desde el botón al span, pero se detiene allí porque la propagación se detiene en este punto.

Concluyendo

Cuando los elementos reciben eventos, tales eventos se propagan a sus padres y ancestros hacia arriba en el árbol del DOM. Este es el concepto de Evento Bubbling, y permite a sus elementos padres manejar eventos que ocurren en los elementos de sus hijos.

Los objetos, eventos también tienen el método, stopPropagation el cual puedes usar para detener el burbujeo de un evento. Esto es útil en casos donde quieres que un elemento reciba un evento clic solamente cuando se le haga clic y no cuando en cualquiera de sus hijos se le haga clic.

stopPropagation y preventDefault son métodos del objeto evento para detener comportamientos por defecto.