Bubbling e Capturing degli eventi JavaScript
Il bubbling e il capturing sono le due fasi del modello di propagazione degli eventi che si verificano quando gli eventi vengono attivati nel DOM.
Comprendere il Bubbling e il Capturing degli eventi in JavaScript
Quando si fa clic su un pulsante, il click non si attiva solo su quel pulsante — percorre ogni elemento antenato verso e dalla destinazione. Questo percorso è chiamato propagazione degli eventi, e avviene in due direzioni: capturing (verso il basso fino all'elemento target) e bubbling (di ritorno verso la radice). Comprendere entrambi è fondamentale per gestire gli eventi in modo affidabile nelle applicazioni reali, specialmente quando elementi annidati hanno i propri handler.
Questa guida spiega il modello di propagazione, mostra come ascoltare in ciascuna fase e illustra gli strumenti pratici — event.target, event.currentTarget, stopPropagation() e la delegazione degli eventi — che rendono questi concetti utili nella pratica quotidiana. Si basa sulle nozioni di base trattate in Introduzione agli eventi del browser e Eventi JavaScript.
Comprendere la propagazione degli eventi
La propagazione degli eventi nel DOM avviene in tre fasi, in questo ordine preciso:
- Fase di capturing — l'evento parte dalla cima dell'albero (
window→document→<html>→ …) e scende verso il basso fino all'elemento target. - Fase target — l'evento raggiunge l'elemento con cui hai effettivamente interagito.
- Fase di bubbling — l'evento risale verso l'alto dal target fino alla radice.
│ capturing (down) ▲ bubbling (up)
<html> ▼ │
<div> ▼ │
<p> ───► target (you clicked here)Per impostazione predefinita, gli handler aggiunti con addEventListener e gli attributi inline on* vengono eseguiti nella fase di bubbling. Si entra esplicitamente nella fase di capturing quando necessario.
Bubbling degli eventi
Nella fase di bubbling, un evento parte dall'elemento più specifico (il nodo più profondo con cui hai interagito) e poi risale attraverso ogni antenato verso il document. Questo è il comportamento predefinito per quasi ogni evento.
<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>Se si fa clic sul <p>, si vedrà prima l'alert per <p>, poi l'alert per <div> mentre l'evento risale con il bubbling. Se si fa clic direttamente sul <div> (al di fuori del <p>), si attiva solo l'alert del <div> — l'evento non raggiunge mai <p> perché <p> non è un antenato del punto di click.
Capturing degli eventi
Il capturing è la prima fase, in cui l'evento scende verso il basso fino al target. Viene utilizzato molto meno spesso del bubbling, ma è utile quando si vuole intercettare un evento prima che qualsiasi handler interno possa essere eseguito.
Per ascoltare durante la fase di capturing, impostare il terzo argomento di addEventListener a true (o passare { 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>Facendo clic sul <p>, gli alert si attivano dall'alto verso il basso: prima DIV (un antenato raggiunto durante la discesa), poi P (il target). Con handler di bubbling l'ordine sarebbe invertito.
Identificare l'elemento corretto
All'interno di un handler di solito è necessario sapere su quale elemento ha avuto origine l'evento e a quale elemento è collegato l'handler. Due proprietà rispondono a questa domanda:
event.target— l'elemento in cui l'evento ha avuto origine (quello più in profondità su cui si è fatto clic). Rimane invariato durante tutta la propagazione.event.currentTarget— l'elemento il cui listener è attualmente in esecuzione. Cambia man mano che l'evento si sposta attraverso l'albero, e corrisponde athisall'interno di un handler con funzione regolare.
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 è ciò che rende possibile la delegazione degli eventi (mostrata di seguito) — un handler su un elemento padre può identificare esattamente quale figlio è stato cliccato.
Controllare la propagazione
JavaScript fornisce diversi metodi per controllare quanto lontano si propaga un evento.
| Metodo | Effetto |
|---|---|
event.stopPropagation() | Impedisce all'evento di continuare verso il prossimo elemento nel percorso (nessun ulteriore bubbling/capturing). Gli handler sullo stesso elemento vengono comunque eseguiti. |
event.stopImmediatePropagation() | Interrompe la propagazione e impedisce l'esecuzione di qualsiasi altro handler sullo stesso elemento. |
event.preventDefault() | Annulla l'azione predefinita del browser (ad es. seguire un link). Non interrompe la propagazione. |
stopPropagation()epreventDefault()sono indipendenti. Interrompere la propagazione non annulla il comportamento predefinito, e viceversa.
Nota sugli eventi che non fanno bubbling
La maggior parte degli eventi fa bubbling, ma alcuni no — ad esempio focus, blur, mouseenter, mouseleave e load. Per questi eventi non si può fare affidamento su un handler padre che li intercetta tramite il bubbling; usare gli equivalenti con bubbling (focusin/focusout, mouseover/mouseout) o collegare il listener direttamente. È sempre possibile verificare event.bubbles per confermare se un determinato evento partecipa alla fase di bubbling.
Esempi pratici
Esempio 1: Interrompere il bubbling degli eventi
A volte si desidera che un click su un elemento figlio non attivi l'handler del genitore:
<!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>In questo esempio, c'è un contenitore con sfondo azzurro che contiene un riquadro rosa. Facendo clic in qualsiasi punto all'interno del contenitore viene visualizzato un alert con il messaggio "You clicked the container!". Tuttavia, facendo clic sul riquadro rosa viene visualizzato un alert diverso con il messaggio "You clicked the box without bubbling!" perché event.stopPropagation() impedisce all'evento click di risalire fino al contenitore con il bubbling.
Esempio 2: Usare sia il bubbling che il capturing
Questo esempio mostra come gestire un evento sia nella fase di capturing che in quella di 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>Quando si fa clic sull'elemento interno:
- Il listener di capturing su
#outerContainerviene eseguito per primo, mostrando l'alert "Event Captured by Outer Container" (si trova sul percorso verso il basso fino al target). - Il listener di bubbling su
#innerElementviene eseguito successivamente, mostrando l'alert "Event Bubbled from Inner Element" (il target stesso). - Infine, l'
onclickinline su#outerContainerviene eseguito mentre l'evento risale con il bubbling, mostrando l'alert "Event Bubbled from Outer Container".
Questo rende visibile l'intero percorso di propagazione nell'ordine corretto: capturing in discesa, raggiungimento del target, poi bubbling in risalita.
Esempio 3: Delegazione degli eventi
L'uso pratico più comune del bubbling è la delegazione degli eventi — collegare un singolo listener a un elemento padre anziché un listener per ogni figlio. Poiché i click risalgono con il bubbling, il padre può usare event.target per capire quale figlio è stato cliccato. Questo approccio è efficiente e funziona automaticamente anche per gli elementi aggiunti in seguito.
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 questo unico handler, ogni <li> attuale e futuro all'interno di #menu è coperto — non è mai necessario collegare (o ricollegare) listener sui singoli elementi. Consulta Gestione degli eventi nel DOM e Dispatching di eventi personalizzati per tecniche correlate.
Conclusione
La propagazione degli eventi sposta un evento verso il basso (capturing) e poi verso l'alto (bubbling) attraverso il DOM. Per impostazione predefinita, gli handler vengono eseguiti durante il bubbling; passa true (o { capture: true }) per ascoltare durante il capturing. Usa event.target per trovare l'elemento che ha avviato l'evento, event.currentTarget per l'elemento che lo sta gestendo, e stopPropagation() / stopImmediatePropagation() per limitarne il percorso. Padroneggiando questi concetti puoi creare interazioni pulite ed efficienti — soprattutto grazie alla delegazione degli eventi — senza disseminare listener in tutta la pagina.