W3docs

Propagación y captura de eventos en JavaScript

El bubbling y capturing son las dos fases del modelo de propagación de eventos que ocurren cuando se disparan eventos en el DOM.

Dominando el bubbling y capturing de eventos en JavaScript

Cuando haces clic en un botón, el clic no solo se dispara en ese botón — viaja a través de cada elemento ancestro en su recorrido hacia y desde el objetivo. Este recorrido se llama propagación de eventos, y ocurre en dos direcciones: capturing (descendiendo hacia el objetivo) y bubbling (ascendiendo de regreso hacia la raíz). Comprender ambos es esencial para gestionar eventos de forma fiable en aplicaciones reales, especialmente cuando los elementos anidados tienen sus propios manejadores.

Esta guía explica el modelo de propagación, muestra cómo escuchar en cada fase y presenta las herramientas prácticas — event.target, event.currentTarget, stopPropagation() y la delegación de eventos — que hacen útiles estos conceptos en el día a día. Se basa en los fundamentos cubiertos en Introducción a los eventos del navegador y Eventos de JavaScript.

Comprendiendo la propagación de eventos

La propagación de eventos en el DOM ocurre en tres fases, en este orden exacto:

  1. Fase de capturing — el evento comienza en la cima del árbol (windowdocument<html> → …) y desciende hacia abajo hasta el elemento objetivo.
  2. Fase objetivo — el evento llega al elemento con el que interactuaste realmente.
  3. Fase de bubbling — el evento asciende hacia arriba desde el objetivo hasta la raíz.
                 │ capturing (down)    ▲ bubbling (up)
   <html>        ▼                     │
     <div>       ▼                     │
       <p> ───►  target (you clicked here)

Por defecto, los manejadores añadidos con addEventListener y los atributos inline on* se ejecutan en la fase de bubbling. Para entrar en la fase de capturing debes hacerlo explícitamente.

Bubbling de eventos

En la fase de bubbling, un evento comienza en el elemento más específico (el nodo más profundo con el que interactuaste) y luego fluye hacia arriba a través de cada ancestro hasta llegar al document. Este es el comportamiento predeterminado para casi todos los eventos.

<div onclick="alert('You clicked the DIV!');">
  Click me or one of my children:
  <p onclick="alert('You clicked the P!');">Click me!</p>
</div>

Si haces clic en el <p>, verás primero la alerta del <p> y luego la alerta del <div> a medida que el evento asciende. Si haces clic directamente en el <div> (fuera del <p>), solo se dispara la alerta del <div> — el evento nunca llega al <p> porque <p> no es un ancestro del punto de clic.

Capturing de eventos

El capturing es la primera fase, en la que el evento desciende hacia el objetivo. Se usa con mucha menos frecuencia que el bubbling, pero resulta útil cuando necesitas interceptar un evento antes de que pueda ejecutarse cualquier manejador interior.

Para escuchar durante la fase de capturing, establece el tercer argumento de addEventListener en true (o pasa { capture: true }):

<div id="outer">
  Click me or one of my children:
  <p id="inner">Click me!</p>
</div>

<script>
  document.getElementById('outer').addEventListener('click', function () {
    alert('Captured on DIV!');
  }, true); // true → capturing phase

  document.getElementById('inner').addEventListener('click', function () {
    alert('Captured on P!');
  }, true);
</script>

Haz clic en el <p> y las alertas se disparan de arriba hacia abajo: primero DIV (un ancestro alcanzado en el camino de descenso) y luego P (el objetivo). Con manejadores de bubbling el orden sería el inverso.

Identificar el elemento correcto

Dentro de un manejador normalmente necesitas saber en qué elemento se originó el evento y en qué elemento está adjunto el manejador. Dos propiedades responden a eso:

  • event.target — el elemento donde el evento se originó (el más profundo en el que se hizo clic). Permanece igual durante toda la propagación.
  • event.currentTarget — el elemento cuyo listener se está ejecutando en este momento. Cambia a medida que el evento recorre el árbol, y es igual a this dentro de un manejador de función regular.
function logTargets(event) {
  console.log("target:", event.target.tagName);
  console.log("currentTarget:", event.currentTarget.tagName);
}
// Imagine this handler is on a <div> and you click a nested <p>:
// target:        P    (where the click happened)
// currentTarget: DIV  (where the listener lives)

event.target es lo que hace posible la delegación de eventos (mostrada más adelante) — un manejador en el padre puede identificar exactamente qué elemento hijo fue clickeado.

Controlar la propagación

JavaScript ofrece varios métodos para controlar hasta dónde viaja un evento.

MétodoEfecto
event.stopPropagation()Detiene el evento para que no continúe hacia el siguiente elemento en la ruta (sin más bubbling/capturing). Los manejadores en el mismo elemento siguen ejecutándose.
event.stopImmediatePropagation()Detiene la propagación y evita que cualquier otro manejador en el mismo elemento se ejecute.
event.preventDefault()Cancela la acción predeterminada del navegador (por ejemplo, seguir un enlace). No detiene la propagación.

stopPropagation() y preventDefault() son independientes. Detener la propagación no cancela el comportamiento predeterminado, y viceversa.

Una nota sobre los eventos que no hacen bubbling

La mayoría de los eventos hacen bubbling, pero algunos no — por ejemplo focus, blur, mouseenter, mouseleave y load. Para estos no puedes confiar en que un manejador padre los capture mediante bubbling; usa los equivalentes con bubbling (focusin/focusout, mouseover/mouseout) o adjunta el listener directamente. Siempre puedes comprobar event.bubbles para confirmar si un evento determinado participa en la fase de bubbling.

Ejemplos prácticos

Ejemplo 1: Detener el bubbling de eventos

A veces quieres que un clic en un elemento hijo no active el manejador del padre:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Event Propagation Example</title>
<style>
  .container {
    width: 200px;
    height: 200px;
    background-color: lightblue;
    padding: 20px;
  }

  .box {
    width: 100px;
    height: 100px;
    background-color: pink;
    margin-top: 20px;
    cursor: pointer;
  }
</style>
</head>
<body>

<div class="container" onclick="alert('You clicked the container!');">
  Click the pink box to see event propagation:
  <div class="box" onclick="event.stopPropagation(); alert('You clicked the box without bubbling!');"></div>
</div>

</body>
</html>

En este ejemplo hay un contenedor con fondo azul claro que contiene una caja rosa. Al hacer clic en cualquier lugar dentro del contenedor se muestra una alerta con el mensaje "You clicked the container!". Sin embargo, al hacer clic en la caja rosa se muestra una alerta diferente con el mensaje "You clicked the box without bubbling!" porque event.stopPropagation() impide que el evento de clic ascienda hasta el contenedor.

Ejemplo 2: Usando bubbling y capturing a la vez

Este ejemplo muestra cómo gestionar un evento en las fases de capturing y bubbling:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Event Capture and Bubbling Example</title>
<style>
  body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 20px;
  }

  #outerContainer {
    border: 2px solid #ccc;
    padding: 20px;
    margin-bottom: 20px;
    background-color: #f9f9f9;
    border-radius: 10px;
  }

  #innerElement {
    background-color: #ffa8a8;
    padding: 10px;
    border-radius: 5px;
    cursor: pointer;
  }
</style>
</head>
<body>

<div id="outerContainer" onclick="alert('Event Bubbled from Outer Container');">
  <p style="margin: 0;">Click anywhere in this outer container:</p>
  <p id="innerElement">Click me!</p>
</div>

<script>
  // Event listener attached to the outer container during the capturing phase
  document.getElementById('outerContainer').addEventListener('click', function() {
    alert('Event Captured by Outer Container');
  }, true);

  // Event listener attached to the inner element during the bubbling phase
  document.getElementById('innerElement').addEventListener('click', function() {
    alert('Event Bubbled from Inner Element');
  }, false);
</script>

</body>
</html>

Cuando haces clic en el elemento interior:

  1. El listener de capturing en #outerContainer se ejecuta primero, mostrando "Event Captured by Outer Container" (está en el camino de descenso hacia el objetivo).
  2. El listener de bubbling en #innerElement se ejecuta a continuación, mostrando "Event Bubbled from Inner Element" (el propio objetivo).
  3. Finalmente el onclick inline en #outerContainer se ejecuta cuando el evento asciende, mostrando "Event Bubbled from Outer Container".

Esto hace visible la ruta completa de propagación en orden: capturing hacia abajo, alcanzar el objetivo y luego bubbling hacia arriba.

Ejemplo 3: Delegación de eventos

El uso práctico más común del bubbling es la delegación de eventos — adjuntar un único listener a un padre en lugar de un listener por cada hijo. Como los clics ascienden mediante bubbling, el padre puede usar event.target para identificar qué hijo fue clickeado. Esto es eficiente y funciona automáticamente para los elementos añadidos posteriormente.

const list = document.getElementById("menu");

list.addEventListener("click", function (event) {
  // Did the click originate on an <li>?
  const item = event.target.closest("li");
  if (!item || !list.contains(item)) return;

  console.log("You clicked:", item.textContent);
});

Con este único manejador, cada <li> actual y futuro dentro de #menu queda cubierto — nunca tienes que enlazar (o volver a enlazar) listeners en elementos individuales. Consulta Gestión de eventos en el DOM y Despacho de eventos personalizados para conocer técnicas relacionadas.

Conclusión

La propagación de eventos mueve un evento hacia abajo (capturing) y luego hacia arriba (bubbling) a través del DOM. Por defecto los manejadores se ejecutan durante el bubbling; pasa true (o { capture: true }) para escuchar durante el capturing. Usa event.target para encontrar el elemento que inició el evento, event.currentTarget para el elemento que lo está gestionando, y stopPropagation() / stopImmediatePropagation() para limitar hasta dónde viaja. Domina estos conceptos y podrás construir interacciones limpias y eficientes — especialmente mediante la delegación de eventos — sin dispersar listeners por toda tu página.

Práctica

Práctica
¿Qué propiedad indica el elemento donde se originó originalmente un evento, independientemente del manejador que esté ejecutándose?
¿Qué propiedad indica el elemento donde se originó originalmente un evento, independientemente del manejador que esté ejecutándose?
Práctica
Por defecto, ¿en qué fase se ejecutan los listeners añadidos con addEventListener?
Por defecto, ¿en qué fase se ejecutan los listeners añadidos con addEventListener?
Was this page helpful?