L’event propagation in JavaScript: bubbling e capturing

In Javascript la Bubbling consente di propagare l’evento dal centro verso l’esterno, la Capturing dall’esterno verso l’interno della struttura.

JavaScript è il mondo, non per nulla è uno dei linguaggi più utilizzati al mondo ed è alle base dei più conosciuti framework front-end. La sua conoscenza è imprescindibile, si può conoscere Angular, React ma senza sapere come sono implementanti è una conoscenza a metà. In quest’articolo analizzeremo una delle caratteristiche di JavaScript, l’event propagation, ovvero l’ordine con cui gli eventi occorrono. Esistono due tipi di propagazione:

  • Bubbling
  • Capturing

La tipologia di propagazione viene definita tramite il parametro useCapture della funzione addEventListener():

addEventListener(event, function, useCapture);

Per capire al meglio l’event propagation utilizzeremo un esempio con una serie di box contenuti uno dentro l’altro. Inizialmente i box saranno colorati con delle gradazioni di blu, quando gli event saranno scatenati utilizzeranno una gradazione del rosso. La struttura HTML sarà così definita:

<div class="hz-box" id="box1">
    <div class="hz-box" id="box2">
        <div class="hz-box" id="box3">
            <div class="hz-box" id="box4">
            </div>
        </div>
    </div>
</div>

Bubbling

Nell’event bubbling cliccando nel box al centro il click viene propagato anche a gli altri box, ma in che modo? Il primo evento click che verrà invocato sarà dal box più interno fino a quello più esterno. In generale gli eventi saranno scatenati in quest’ordine:

inner box -> outer box -> body -> document -> window

Per abilitare il bubbling dovremo passare false o null a addEventListener(), ad esempio:

box1.addEventListener('click', (event) => {
    // ...
}, false);

Oppure senza passare nessun parametro (comportamento di default che abilita la fase bubbling):

box1.addEventListener('click', (event) => {
    // ...
});

Per rendere dinamica l’event propagation andremo ad utilizzare dei setTimeout() a cui andremo ad aggiungere un delay tramite un counter per garantire l’ordine (ricorda che anche se dei setTimeout() sono stati definiti in un certo ordine nulla garantisce che termino nello stesso ordine!). All’interno del timeout andremo a cambiare il colore dello sfondo del box. Il codice è così definito:

const isCapturingFase = true; // Set to false or null to enable Bubbling phase
let count = 0;
const box1 = document.getElementById("box1");
const box2 = document.getElementById("box2");
const box3 = document.getElementById("box3");
const box4 = document.getElementById("box4");

box1.addEventListener('click', (event) => {
    console.log('Box1 event click fired');
    setTimeout(() => {
        box1.style.backgroundColor = "#ff9999";
    }, 1000 * ++count);
}, isCapturingFase);

box2.addEventListener('click', (event) => {
    console.log('Box2 event click fired');
    setTimeout(() => {
        box2.style.backgroundColor = "#ff3333";
    }, 1000 * ++count);
}, isCapturingFase);

box3.addEventListener('click', (event) => {
    console.log('Box3 event click fired');
    setTimeout(() => {
        box3.style.backgroundColor = "#cc0000";
    }, 1000 * ++count);
}, isCapturingFase);

box4.addEventListener('click', (event) => {
    console.log('Box4 event click fired');
    setTimeout(() => {
        box4.style.backgroundColor = '#660000';
    }, 1000 * ++count);
}, isCapturingFase);

Abbiamo aggiunto dei console.log() per renderci conto di quando l’evento click del box viene invocato. Eseguendo il codice avremo in console prima il click del box più interno fino a quello più esterno:

Box4 event click fired
Box3 event click fired
Box2 event click fired
Box1 event click fired

Visivamente avremo:
JavaScript bubbling

Puoi provare il codice al seguente link.

Capturing

Nell’event capturing JavaScript consente di fare scatenare il primo evento click invocato è a partire da quello più esterno a quello più interno. Avremo:

window -> document -> body -> outer box -> inner box

Per abilitare il capturing dovremo settare a true l’evento useCapture:

box1.addEventListener('click', (event) => {
    // ...
}, true);

Andando a settare a true la variabile del codice di esempio attiveremo la fase capturing:

const isCapturingFase = false;

In console avremo il click dal box più esterno a quello più interno:

Box1 event click fired
Box2 event click fired
Box3 event click fired
Box4 event click fired

Visivamente avremo:
JavaScript capturing

Puoi provare il codice al seguente link.

Differenza tra stopPropagation e preventDefault

Esistono delle funzioni che ci consentono di stoppare la propagazione degli eventi, in particolare esistono due funzioni:

  • stopPropagation() che consente di stoppare la propagazione indipendentemente dalle due fase (se capturing o bubbling)
  • preventDefault() ferma l’evento solo se in fase captuing bloccando solo il comportamento di default dell’elemento (ad es. spunta su una checkbox)

Vediamo un esempio pratico che stoppa la propagazione. Nell’HTML avremo:

<div class="hz-box" id="box1">
    <div class="hz-box" id="box2">
    </div>
</div>

Aggiungendo event.stopPropagation() nel click del box2:

box1.addEventListener('click', (event) => {
    console.log('box1');
});
box2.addEventListener('click', (event) => {
    console.log('box2');
    event.stopPropagation();
});


Cliccando nel box centrale avremo in console solo il click del box2 in quanto la propagazione viene stoppata e non raggiungerà mai il box1.

Per un esempio con preventDefault() bloccheremo il comportamento di default di una checkbox. Nell’HTML avremo:

<form>
    <label for="hz-checkbox">Click on the checkbox:</label>
    <input type="checkbox" id="hz-checkbox"/>
</form>


Andando a settare event.preventDefault() nell’evento click sulla checkbox e settando la capturing event:

const hzCheckbox= document.getElementById("hz-checkbox");
hzCheckbox.addEventListener("click", function(event) {
    console.log('preventDefault() blocks your click');
    event.preventDefault();
}, false);


Con questo codice non potremo mai flaggare la checkbox.

E’ possibile testare il codice per entrambi gli eventi al seguente link.

Pubblicato in Front-end, JavaScript

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

*