hand Inicio

Event Bubbling y el descontrol en JavaScript

El event bubbling y event capturing se refiere a cuando un evento se propaga hacia sus elementos padre, o al revés y hacia sus elementos hijo

Eventos

Para entender el concepto de bubbling, hay que hablar antes de los eventos

Los eventos son la manera que tenemos de monitorizar el comportamiento de nuestros usuarios, por ejemplo al detectar cuando se hace click en un botón

Para capturar esos eventos se utilizan los listeners, con la siguiente estructura

js
el.addEventListener(event, () => {})

Donde para capturar el click en un botón, el event será del tipo click

js
const el = document.getElementById('mi_boton')
el.addEventListener('click', () => {
console.log('elemento clicado!')
})

Bubbling y Capturing

Entendido como se pueden monitorear los eventos, entonces entender que es el bubbling y el capturing es sencillo

Si te fijas en este código, tienes un div con id mi_div, dentro tienes un botón con id mi_boton_1 y dentro de este botón tienes otro botón con id mi_boton_2

html
<div id="mi_div">
<button id="mi_boton_1">
Boton 1
<button id="mi_boton_2">Boton 2</button>
</button>
</div>

Ahora se añaden los listeners para los 3 elementos

js
document.getElementById('mi_boton_1').addEventListener('click', () => console.log('boton 1 clicado!'))
document.getElementById('mi_boton_2').addEventListener('click', () => console.log('boton 2 clicado!'))
document.getElementById('mi_div').addEventListener('click', () => console.log('div clicado!'))

Y entonces surge la pregunta, qué pasará si aprietas el div o cualquiera de los botones?

Si aprietas cualquiera de los 2 botones tendrás 2 eventos, el del propio botón y el del div que está por encima

El evento del div ocurre porque cuando clickas el botón, también estás clickando el div por lo que los dos reciben el evento

Primero lo recibe el botón, y luego lo recibe el div

Pero si aprietas el botón 2 por qué no tienes también el evento del botón 1?

Porque los botones no pueden anidar otros botones, por lo que el navegador simplemente lo ignora (stackoverflow)

El bubbling por lo tanto es el hecho de tener eventos que traspasan el elemento primero y se propagan a sus elementos que los incluyan

Y lo contrario es el capturing, es decir, es lo mismo pero el evento va en la dirección opuesta, nace en el elemento más general y se va propagando hasta llegar al elemento original

Podemos conseguir este orden inverso en los listeners especificando la propiedad capture

js
document.getElementById('mi_boton_1').addEventListener('click', () => console.log('boton1 clicado!'), { capture: true })
document.getElementById('mi_boton_2').addEventListener('click', () => console.log('boton2 clicado!'), { capture: true })
document.getElementById('mi_div').addEventListener('click', () => console.log('div clicado!'), { capture: true })

Ahora si clickamos el botón veremos que el primer evento no ocurre en el botón sino en el div padre, y luego viene el evento del botón (lo ves en el orden en que aparecen los console.log)

Anular la propagación de los eventos (y con React)

Lo más habitual es no querer que el evento se propague, algo que se resuelve de forma muy sencilla añadiendo un stopPropagation()

js
document.getElementById('mi_boton_1').addEventListener('click', e => {
e.stopPropagation()
console.log('boton 1 clicado!')
})
document.getElementById('mi_boton_2').addEventListener('click', e => {
e.stopPropagation()
console.log('boton 2 clicado!')
})
document.getElementById('mi_div').addEventListener('click', e => {
e.stopPropagation()
console.log('div clicado!')
})

Sin embargo si trabajas con React ya habrás visto que no utilizas los addEventListener

Definitivamente hacer lo mismo con React es más complejo, aunque una vez estás acostumbrado el código parece en realidad tener más sentido (cuestión de opiniones)

En React tendríamos lo siguiente (ahora con un solo botón):

jsx
import React, { useRef } from 'react'

export const App = () => {
const divRef = useRef()
const buttonRef = useRef()

return (
<div ref={divRef} onClick={() => console.log('yo también!')}>
<h1>Hola</h1>
<button ref={buttonRef} onClick={() => console.log('clicando!')}>
clica!
</button>
</div>
)
}

export default App

Si lo que quieres es capturar los eventos a la inversa como hemos visto antes, aquí lo haces cambiando el onClick por el onClickCapture

jsx
import React, { useRef } from 'react'

export const App = () => {
const divRef = useRef()
const buttonRef = useRef()

return (
<div ref={divRef} onClickCapture={() => console.log('yo también!')}>
<h1>Hola</h1>
<button ref={buttonRef} onClickCapture={() => console.log('clicando!')}>
clica!
</button>
</div>
)
}

export default App

Y para evitarlo, lo mismo que con el javascript de antes, añadiendo el stopPropagation()

jsx
import React, { useRef } from 'react'

const log = (e, msg) => {
e.stopPropagation()
console.log(msg)
}

export const App = () => {
const div = useRef()
const button = useRef()

return (
<div ref={div} onClick={e => log(e, 'yo también!')}>
<h1>Hola</h1>
<button ref={button} onClick={e => log(e, 'clicando!')}>
clica!
</button>
</div>
)
}

export default App

Listos!

draw of me

Hola, tienes en mente desarrollar una web?

Si quieres, te ayudo

Escríbeme