Il modello a eventi in JavaScript: il bubbling e la cattura spiegati bene

by Anastasia P.
SHARE
Il modello a eventi in JavaScript: il bubbling e la cattura spiegati bene
© Guida-HTML5.it

Introduzione

Quando si parla di modello a eventi in JavaScript, spesso ci si concentra solo su addEventListener e sul fatto che un clic “accade” su un elemento. In realtà, sotto questa semplicità apparente, il browser segue un flusso preciso chiamato propagazione dell’evento. Uno degli aspetti più utili da conoscere, soprattutto in applicazioni reali, è la differenza tra bubbling e cattura.

Capire questo meccanismo ti aiuta a scrivere codice più pulito, a controllare meglio il comportamento dell’interfaccia e a risolvere problemi frequenti come eventi che si attivano “troppo” o nel punto sbagliato della gerarchia DOM. È un argomento pratico, spesso sottovalutato, ma fondamentale per chi sviluppa interfacce dinamiche.

In questo tutorial vedremo come funziona la propagazione degli eventi, quando usare la fase di cattura, come sfruttare il bubbling per semplificare la gestione degli eventi e come evitare errori comuni con stopPropagation() e stopImmediatePropagation().

Codice completo

// Struttura HTML di esempio:
// <div id="app">
//   <div id="panel">
//     <button id="saveBtn">Salva</button>
//   </div>
// </div>

const app = document.querySelector(´#app´);
const panel = document.querySelector(´#panel´);
const saveBtn = document.querySelector(´#saveBtn´);

// 1) Listener in fase di cattura
app.addEventListener(´click´, (event) => {
  console.log(´APP - cattura´);
}, true);

panel.addEventListener(´click´, (event) => {
  console.log(´PANEL - cattura´);
}, true);

saveBtn.addEventListener(´click´, (event) => {
  console.log(´BUTTON - cattura´);
}, true);

// 2) Listener in fase di bubbling (default)
app.addEventListener(´click´, (event) => {
  console.log(´APP - bubbling´);
});

panel.addEventListener(´click´, (event) => {
  console.log(´PANEL - bubbling´);
});

saveBtn.addEventListener(´click´, (event) => {
  console.log(´BUTTON - bubbling´);
});

// 3) Esempio pratico: bloccare la propagazione sul bottone
saveBtn.addEventListener(´click´, (event) => {
  console.log(´BUTTON - azione principale´);

  // Blocca la propagazione verso i genitori
  event.stopPropagation();
});

// 4) Esempio con stopImmediatePropagation()
// Se un elemento ha più listener sullo stesso evento,
// questo impedisce l´esecuzione dei listener successivi sullo stesso target.
saveBtn.addEventListener(´click´, (event) => {
  console.log(´Primo listener del bottone´);
  event.stopImmediatePropagation();
});

saveBtn.addEventListener(´click´, () => {
  console.log(´Questo listener non verrà eseguito´);
});

// 5) Gestione robusta di un evento su una lista dinamica
const list = document.querySelector(´#taskList´);

list.addEventListener(´click´, (event) => {
  const deleteButton = event.target.closest(´[data-action="delete"]´);

  if (!deleteButton) return;

  const taskItem = deleteButton.closest(´.task´);
  if (taskItem) {
    taskItem.remove();
    console.log(´Task eliminata´);
  }
});

Spiegazione

Il browser gestisce gli eventi del DOM in tre fasi principali:

  • Fase di cattura: l’evento parte dall’alto, dal documento, e scende verso l’elemento bersaglio.
  • Fase target: l’evento raggiunge l’elemento su cui è avvenuta l’interazione.
  • Fase di bubbling: l’evento risale dal target verso i suoi antenati.

Nel codice di esempio, abbiamo una struttura annidata: app contiene panel, che contiene il button. Se clicchi il bottone, il browser può notificare i listener in questo ordine:

  • prima i listener in cattura sui genitori, dall’esterno verso l’interno;
  • poi il listener sul target;
  • infine i listener in bubbling, dal bottone verso l’esterno.

La differenza tra cattura e bubbling si controlla nel terzo parametro di addEventListener. Se passi true, il listener viene eseguito in cattura. Se ometti il parametro o usi false, il listener viene eseguito in bubbling, che è il comportamento più comune.

Perché il bubbling è così utile?

Il bubbling è molto pratico perché permette di gestire eventi su un contenitore invece che su ogni singolo elemento figlio. Anche se la delegazione degli eventi merita un discorso a parte, qui è importante capire che il bubbling è il meccanismo che la rende possibile.

Per esempio, in una lista di task con pulsanti “Elimina”, non serve registrare un listener su ogni bottone. Puoi ascoltare il click sul contenitore della lista e risalire al pulsante effettivamente premuto usando event.target e closest(). Questo approccio è più manutenibile e funziona bene anche con elementi creati dinamicamente.

Quando usare la cattura?

La fase di cattura è meno usata del bubbling, ma è utile quando devi intercettare un evento prima che raggiunga il target. Può servire, ad esempio, per log, controlli globali, blocchi preventivi o integrazioni con componenti complessi. In generale, però, va usata con criterio: se non hai una ragione precisa, il bubbling è più semplice da leggere e mantenere.

stopPropagation() e stopImmediatePropagation()

event.stopPropagation() impedisce all’evento di continuare a propagarsi verso gli antenati. È utile quando un’azione locale non deve attivare comportamenti del contenitore.

event.stopImmediatePropagation() è ancora più forte: oltre a bloccare la propagazione verso l’alto, impedisce anche l’esecuzione degli altri listener registrati sullo stesso elemento per lo stesso evento. Va usato con attenzione perché può rendere il flusso più difficile da prevedere.

Un caso reale: pulsante dentro una card cliccabile

Immagina una card prodotto cliccabile per aprire i dettagli, ma con un pulsante interno “Aggiungi al carrello”. Se clicchi il pulsante, probabilmente non vuoi aprire anche la card. In questo caso, il pulsante può chiamare stopPropagation() per evitare che il click risalga al contenitore.

Questo è un esempio classico di come conoscere la propagazione ti aiuti a controllare l’esperienza utente senza aggiungere logica confusa o duplicata.

Best practice

  • Preferisci il bubbling quando possibile: è il comportamento più naturale e leggibile nella maggior parte dei casi.
  • Usa la cattura solo se serve davvero, ad esempio per intercettare eventi prima che arrivino al target.
  • Evita di abusare di stopPropagation(): bloccare la propagazione troppo spesso può creare effetti collaterali difficili da diagnosticare.
  • Controlla sempre event.target quando gestisci eventi su contenitori, soprattutto con elementi annidati.
  • Usa closest() per risalire al nodo corretto quando il click avviene su un figlio interno, come un’icona dentro un bottone.
  • Attenzione ai listener multipli sullo stesso elemento: se usi stopImmediatePropagation(), gli altri non verranno eseguiti.
  • Progetta il DOM pensando agli eventi: una struttura chiara rende più semplice controllare la propagazione.

Riepilogo

Il modello a eventi in JavaScript non è solo “ascoltare un click”, ma capire come quel click si muove nel DOM. La propagazione avviene in tre fasi: cattura, target e bubbling. Tra queste, il bubbling è il più usato perché semplifica la gestione degli eventi e abilita tecniche molto efficaci come la delegazione.

La fase di cattura è utile in scenari specifici, mentre stopPropagation() e stopImmediatePropagation() servono per controllare il flusso quando un evento non deve raggiungere altri listener o elementi genitori. Conoscere bene questi strumenti ti permette di scrivere interfacce più robuste, prevedibili e facili da mantenere.

Approfondisci con risorse ufficiali

  • MDN - Event bubbling and capturing: documentazione chiara sulla propagazione degli eventi nel DOM.
  • MDN - EventTarget.addEventListener(): dettagli completi sui parametri e sulle opzioni del listener.
  • MDN - Event.stopPropagation(): spiegazione ufficiale del blocco della propagazione.
  • MDN - Event.stopImmediatePropagation(): differenze rispetto a stopPropagation().
  • WHATWG DOM Standard: specifica ufficiale del comportamento degli eventi nel DOM.

SHARE