Základy bublání událostí v JavaScriptu
Když jsem začínal s vývojem webových stránek, jednou z nečekaných a fascinujících věcí, se kterými jsem se setkal, bylo takzvané „bublání událostí“. Zpočátku to bylo trochu matoucí, ale po hlubším zamyšlení dává tento mechanismus smysl. Jako webový vývojář se s ním určitě setkáte. Co to tedy vlastně bublání událostí je?
JavaScript umožňuje interakci uživatelů s webovými stránkami prostřednictvím událostí. Událost je jakákoli akce nebo jev, na který může kód reagovat. Mezi příklady událostí patří kliknutí myší, stisknutí klávesy nebo odeslání formuláře.
JavaScript používá takzvané „posluchače událostí“ (event listeners) pro detekci a reakci na události. Posluchač událostí je v podstatě funkce, která čeká na určitou událost na stránce. Například na kliknutí tlačítka. Jakmile posluchač zaznamená danou událost, spustí kód, který je s ní spojen. Celý tento proces se nazývá zpracování událostí.
Představme si situaci, kde máme na stránce tři HTML elementy: div, span a button. Tlačítko je vnořené do spanu a span je vnořený do divu. Následuje příklad takové struktury:
Pokud každý z těchto elementů má posluchač události, který reaguje na kliknutí a vypíše do konzole zprávu, co se stane, když klikneme na tlačítko?
Chcete-li si to vyzkoušet sami, vytvořte novou složku a v ní tři soubory: index.html
, style.css
a app.js
.
Do souboru index.html
vložte následující kód:
<html lang="cs"> <head> <title>Bublání událostí</title> <link rel="stylesheet" href="style.css"> </head> <body> <div> <span><button>Klikni mě!</button></span> </div> <script src="app.js"></script> </body> </html>
Do souboru style.css
přidejte tento kód pro stylování div a span elementů:
div { border: 2px solid black; background-color: orange; padding: 30px; width: 400px; } span { display: inline-block; background-color: cyan; height: 100px; width: 200px; margin: 10px; padding: 20px; border: 2px solid black; }
A konečně, do souboru app.js
vložte tento kód pro přidání posluchačů událostí k div, span a button elementům. Všechny posluchače reagují na událost kliknutí:
const div = document.querySelector('div'); div.addEventListener('click', () => { console.log("Klikli jste na div element"); }); const span = document.querySelector('span'); span.addEventListener('click', () => { console.log("Klikli jste na span element"); }); const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Klikli jste na tlačítko"); });
Nyní otevřete soubor index.html
v prohlížeči. Prohlédněte si stránku a klikněte na tlačítko. Co se stane? Níže je zobrazen výsledek:
Kliknutím na tlačítko se spustí posluchač události připojený k tlačítku. Spustí se ale také posluchače událostí u span a div elementů. Proč tomu tak je?
Kliknutím na tlačítko spustíme posluchač události přiřazený k tlačítku a tato událost se vypíše do konzole. Tlačítko je však vnořené v span elementu. Kliknutím na tlačítko tedy „technicky“ klikáme i na span element, a proto se spustí jeho posluchač událostí.
Protože span element je vnořený v div elementu, kliknutím na span element se zároveň kliká i na div element. Proto se spustí i jeho posluchač událostí. Tomu se říká bublání událostí.
Co je to bublání událostí?
Bublání událostí je proces, kdy se událost, která vznikla uvnitř vnořené struktury HTML elementů, šíří směrem vzhůru od nejvnitřnějšího prvku, kde vznikla. Událost postupuje podél DOM stromu až k samotnému kořenovému elementu, přičemž spouští všechny posluchače událostí, které na ni reagují.
Posluchače událostí se spouštějí v pořadí, které odpovídá šíření události v rámci DOM stromu. Zvažte následující diagram DOM stromu, který představuje strukturu HTML použitou v tomto článku.
DOM strom ukazuje, že tlačítko je vnořené do spanu, který je vnořený do divu, který je vnořený do body a body do html. Když klikneme na tlačítko, nejprve se spustí posluchač události přiřazený přímo k tlačítku.
Protože jsou ale elementy do sebe vnořené, událost se přesune vzhůru v DOM stromu, a to na span, poté na div, pak na body a nakonec na html, přičemž spustí posluchače událostí, které na kliknutí reagují v tomto pořadí.
Proto se spouští posluchače událostí u span a div elementů. Pokud bychom měli posluchače událostí pro body a html, tak by se spustily i ty.
Element, kde událost vznikne, se nazývá cíl (target). V našem případě, protože ke kliknutí dochází na tlačítko, je prvek tlačítka cílem události.
Jak zastavit bublání událostí?
Pro zastavení šíření události po DOM stromu se používá metoda stopPropagation()
, která je dostupná na objektu události. Níže je ukázka, jak jsme přidávali posluchač událostí k tlačítku:
const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Klikli jste na tlačítko"); });
Tento kód povede k tomu, že se událost „vybublá“ celým DOM stromem, když uživatel klikne na tlačítko. Pro zastavení bublání událostí zavoláme metodu stopPropagation()
takto:
const button = document.querySelector('button'); button.addEventListener('click', (e) => { console.log("Klikli jste na tlačítko"); e.stopPropagation(); });
Obsluha události (event handler) je funkce, která se provede po kliknutí na tlačítko. Posluchač události automaticky předává objekt události do obsluhy. V našem případě je tento objekt události reprezentován proměnnou e
, která se předává jako parametr v obsluze události.
Tento objekt události, e
, obsahuje informace o události a také nám poskytuje přístup k různým vlastnostem a metodám souvisejícím s událostmi. Jednou z těchto metod je stopPropagation()
, která se používá k zabránění bublání událostí. Voláním stopPropagation()
u posluchače událostí tlačítka zabráníme tomu, aby se událost rozšířila po DOM stromu.
Níže je výsledek kliknutí na tlačítko po přidání metody stopPropagation()
:
Používáme stopPropagation()
pro zabránění události v „bublání“ z prvku, na kterém ji použijeme. Pokud bychom například chtěli, aby se událost kliknutí „probublala“ pouze od button elementu nahoru k span elementu, ale ne dál, použili bychom stopPropagation()
u posluchače události span elementu.
Zachycování událostí
Zachycování událostí (event capturing) je opačný mechanismus než bublání událostí. Při zachycování události, událost „stéká“ shora dolů, od nejvzdálenějšího prvku k cílovému prvku. Názorně je to znázorněno níže:
V našem případě, při zachycování událostí, pokud kliknete na button element, posluchače událostí u div elementu se spustí první. Poté posluchače u span elementu a nakonec posluchače u cílového button elementu.
Výchozím způsobem šíření událostí v DOM je však bublání událostí. Pro změnu výchozího chování z bublání událostí na zachycování událostí předáme posluchači události třetí argument s hodnotou true
. Pokud třetí argument nepředáte, bude zachycování událostí automaticky nastaveno na false
.
Uvažte následující posluchač události:
div.addEventListener('click', () => { console.log("Klikli jste na div element"); });
Protože nemá žádný třetí argument, zachycování je nastaveno na false
. Pro nastavení zachycování na true
předáme třetí argument – boolean true
:
div.addEventListener('click', () => { console.log("Klikli jste na div element"); }, true);
Případně můžete předat objekt, který nastaví capture na true
, takto:
div.addEventListener('click', () => { console.log("Klikli jste na div element"); }, {capture: true});
Pro otestování zachycování událostí přidejte třetí argument ke všem posluchačům událostí v souboru app.js
takto:
const div = document.querySelector('div'); div.addEventListener('click', () => { console.log("Klikli jste na div element"); }, true); const span = document.querySelector('span'); span.addEventListener('click', (e) => { console.log("Klikli jste na span element"); }, true); const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Klikli jste na tlačítko"); }, true);
Nyní otevřete prohlížeč a klikněte na button element. Měli byste vidět tento výstup:
Všimněte si, že na rozdíl od bublání událostí, kde byl výstup tlačítka vytištěn jako první, při zachycování událostí je výstup div elementu první.
Bublání událostí a zachycování událostí jsou hlavní způsoby, jak se události šíří v DOM. Většinou se ale používá bublání událostí.
Delegování událostí
Delegování událostí je návrhový vzor, kde je jeden posluchač události přidán ke společnému nadřazenému elementu, například k <ul>
, místo toho, aby každý z podřízených prvků měl svůj posluchač. Události v podřízených elementech se poté „vybublají“ k nadřazenému elementu, kde jsou zpracovány jeho obsluhou události.
Tedy v situaci, kdy máme nadřazený element s podřízenými elementy, přidáme posluchač události pouze k nadřazenému elementu. Tato obsluha události zpracuje všechny události u podřízených elementů.
Možná se ptáte, jak nadřazený element zjistí, na který podřízený element bylo kliknuto? Jak již bylo zmíněno dříve, posluchač události předává objekt události do obsluhy. Tento objekt má metody a vlastnosti, které obsahují informace o konkrétní události. Jednou z těchto vlastností je vlastnost target
. Ta ukazuje na HTML element, kde událost vznikla.
Například pokud máme neuspořádaný seznam s položkami seznamu (<li>
) a na <ul>
elementu je posluchač události, když k události dojde u položky seznamu, vlastnost target
v objektu události bude ukazovat na konkrétní položku, kde k události došlo.
Chcete-li vidět delegování událostí v akci, přidejte následující kód do stávajícího souboru index.html
:
<ul> <li>Toyota</li> <li>Subaru</li> <li>Honda</li> <li>Hyundai</li> <li>Chevrolet</li> <li>Kia</li> </ul>
Nyní přidejte následující JavaScript kód do souboru app.js
. Používá delegování událostí k použití jednoho posluchače událostí u nadřazeného elementu pro zachycení událostí u podřízených elementů:
const ul = document.querySelector('ul'); ul.addEventListener('click', (e) => { // Získáme cílový element targetElement = e.target; // Vypíšeme obsah cílového elementu console.log(targetElement.textContent); });
Nyní otevřete prohlížeč a klikněte na jakoukoli položku seznamu. Obsah elementu by se měl vypsat do konzole takto:
Pomocí jediného posluchače událostí můžeme zpracovat události u všech podřízených elementů. Příliš mnoho posluchačů událostí na stránce negativně ovlivňuje její výkon, protože spotřebovává více paměti a vede k pomalému načítání a vykreslování stránek.
Delegování událostí nám umožňuje se tomu vyhnout tím, že minimalizujeme počet posluchačů událostí na stránce. Delegování událostí je založené na bublání událostí. Můžeme tedy říct, že bublání událostí může pomoci při optimalizaci výkonu webových stránek.
Tipy pro efektivní zpracování událostí
Při práci s událostmi v DOM zvažte použití delegování událostí namísto přidávání mnoha posluchačů událostí k jednotlivým elementům na stránce.
Při použití delegování událostí vždy přidávejte posluchač události k nejbližšímu společnému předku podřízených elementů, které potřebují zpracování událostí. To pomáhá při optimalizaci bublání událostí a minimalizuje cestu, kterou událost musí urazit, než je zpracována.
Při zpracování událostí využívejte objekt události, který posluchač události poskytuje. Obsahuje vlastnosti jako target
, které jsou velmi užitečné při zpracování událostí.
Pro výkonnější webové stránky se vyhýbejte nadměrné manipulaci s DOM. Události, které spouští častou manipulaci s DOM, mohou negativně ovlivnit výkon vašeho webu.
A konečně, v případě vnořených elementů buďte velmi opatrní při přidávání vnořených posluchačů událostí. Nejenže to může ovlivnit výkon, ale také to může vést ke komplikovanému zpracování událostí a kód se stane hůře udržovatelným.
Závěr
Události jsou mocný nástroj v JavaScriptu. Bublání událostí, zachycování událostí a delegování událostí jsou důležité koncepty při zpracování událostí v JavaScriptu. Jako webový vývojář se s těmito koncepty seznamte, abyste mohli vytvářet interaktivnější, dynamičtější a výkonnější weby a aplikace.