Probublávání a zachycování

Začněme příkladem.

Tento obslužný program je přiřazen k <div> , ale spustí se také, když kliknete na jakoukoli vnořenou značku, například <em> nebo <code> :

<div onclick="alert('The handler!')">
 <em>If you click on <code>EM</code>, the handler on <code>DIV</code> runs.</em>
</div>

Není to trochu zvláštní? Proč má handler na <div> spustit, pokud bylo skutečné kliknutí na <em> ?

Bublání

Princip bublání je jednoduchý.

Když na prvku dojde k události, spustí nejprve obslužné rutiny na něm, poté na jeho nadřazeném prvku a poté zcela nahoru na ostatních předcích.

Řekněme, že máme 3 vnořené prvky FORM > DIV > P s handlerem na každém z nich:

<style>
 body * {
 margin: 10px;
 border: 1px solid blue;
 }
</style>

<form onclick="alert('form')">FORM
 <div onclick="alert('div')">DIV
 <p onclick="alert('p')">P</p>
 </div>
</form>

Kliknutím na vnitřní <p> nejprve spustí onclick :

  1. Na tom <p> .
  2. Poté na vnější <div> .
  3. Poté na vnější <form> .
  4. A tak dále až do document objekt.

Pokud tedy klikneme na <p> , pak se zobrazí 3 upozornění:pdivform .

Tento proces se nazývá „bublání“, protože události „bublají“ z vnitřního elementu nahoru přes rodiče jako bublina ve vodě.

Téměř bublina všech událostí.

Klíčové slovo v této frázi je „téměř“.

Například focus akce nebublá. Existují i ​​další příklady, setkáme se s nimi. Ale i tak je to spíše výjimka než pravidlo, většina událostí bubliny.

event.target

Obslužná rutina nadřazeného prvku může vždy získat podrobnosti o tom, kde se to skutečně stalo.

Nejhlouběji vnořený prvek, který událost způsobil, se nazývá cíl prvek, přístupný jako event.target .

Všimněte si rozdílů od this (=event.currentTarget ):

  • event.target – je „cílovým“ prvkem, který událost inicioval, nemění se během procesu bublání.
  • this – je „aktuální“ prvek, ten, na kterém je aktuálně spuštěný handler.

Například, pokud máme jeden handler form.onclick , pak může „chytit“ všechna kliknutí uvnitř formuláře. Bez ohledu na to, kde ke kliknutí došlo, zobrazí se až na <form> a spustí obsluhu.

V form.onclick handler:

  • this (=event.currentTarget ) je <form> prvek, protože na něm běží handler.
  • event.target je skutečný prvek uvnitř formuláře, na který bylo kliknuto.

Podívejte se na to:

Resultscript.jsexample.cssindex.html
form.onclick = function(event) {
 event.target.style.backgroundColor = 'yellow';

 // chrome needs some time to paint yellow
 setTimeout(() => {
 alert("target = " + event.target.tagName + ", this=" + this.tagName);
 event.target.style.backgroundColor = ''
 }, 0);
};
form {
 background-color: green;
 position: relative;
 width: 150px;
 height: 150px;
 text-align: center;
 cursor: pointer;
}

div {
 background-color: blue;
 position: absolute;
 top: 25px;
 left: 25px;
 width: 100px;
 height: 100px;
}

p {
 background-color: red;
 position: absolute;
 top: 25px;
 left: 25px;
 width: 50px;
 height: 50px;
 line-height: 50px;
 margin: 0;
}

body {
 line-height: 25px;
 font-size: 16px;
}
<!DOCTYPE HTML>
<html>

<head>
 <meta charset="utf-8">
 <link rel="stylesheet" href="example.css">
</head>

<body>
 A click shows both <code>event.target</code> and <code>this</code> to compare:

 <form id="form">FORM
 <div>DIV
 <p>P</p>
 </div>
 </form>

 <script src="script.js"></script>
</body>
</html>

Je možné, že event.target se může rovnat this – stane se tak, když se klikne přímo na <form> prvek.

Zastavení bublání

Bublinková událost jde od cílového prvku přímo nahoru. Normálně to jde nahoru až do <html> a poté na document objekt a některé události dokonce dosahují window , zavolá všechny handlery na cestě.

Ale kterýkoli handler může rozhodnout, že událost byla plně zpracována a zastavit bublání.

Metoda je event.stopPropagation() .

Například zde body.onclick nefunguje, pokud kliknete na <button> :

<body onclick="alert(`the bubbling doesn't reach here`)">
 <button onclick="event.stopPropagation()">Click me</button>
</body>
event.stopImmediatePropagation()

Pokud má prvek v jedné události více obslužných rutin událostí, pak i když jeden z nich zastaví bublání, ostatní se stále provádějí.

Jinými slovy, event.stopPropagation() zastaví pohyb nahoru, ale na aktuálním prvku poběží všechny ostatní ovladače.

Chcete-li zastavit bublání a zabránit spuštění obslužných rutin na aktuálním prvku, existuje metoda event.stopImmediatePropagation() . Poté se nespouštějí žádné další ovladače.

Nepřestávejte bublat bez potřeby!

Bublinkování je pohodlné. Nezastavujte to bez skutečné potřeby:zřejmé a architektonicky dobře promyšlené.

Někdy event.stopPropagation() vytváří skryté nástrahy, které se později mohou stát problémy.

Například:

  1. Vytváříme vnořenou nabídku. Každá podnabídka zpracovává kliknutí na své prvky a volá stopPropagation aby se nespustila vnější nabídka.
  2. Později se rozhodneme zachytit kliknutí na celé okno, abychom mohli sledovat chování uživatelů (kam lidé klikají). Některé analytické systémy to dělají. Obvykle kód používá document.addEventListener('click'…) zachytit všechna kliknutí.
  3. Naše analýza nebude fungovat v oblasti, kde jsou kliknutí zastavena stopPropagation . Bohužel máme „mrtvou zónu“.

Obvykle není potřeba bublání bránit. Úkol, který zdánlivě vyžaduje a který lze vyřešit jinými prostředky. Jedním z nich je použití vlastních událostí, kterým se budeme věnovat později. Také můžeme zapsat naše data do event vznést námitku v jednom handleru a přečíst ho v jiném, abychom mohli předat handlerům na rodičích informace o zpracování níže.

Snímání

Existuje další fáze zpracování události zvaná „zachycení“. Ve skutečném kódu se používá zřídka, ale někdy může být užitečný.

Standardní události DOM popisují 3 fáze šíření události:

  1. Fáze zachycení – událost jde dolů k prvku.
  2. Cílová fáze – událost dosáhla cílového prvku.
  3. Fáze bublání – událost vybublá z prvku.

Zde je obrázek, převzatý ze specifikace, zachycení (1) , cíl (2) a bublající (3) fáze pro událost kliknutí na <td> uvnitř tabulky:

To znamená:pro kliknutí na <td> událost nejprve prochází řetězcem předků dolů k prvku (fáze zachycení), pak dosáhne cíle a spustí se tam (cílová fáze) a poté stoupá (fáze bublání) a zavolá obsluhu.

Doposud jsme mluvili pouze o probublávání, protože fáze zachycení se používá zřídka.

Ve skutečnosti byla fáze zachycení pro nás neviditelná, protože handlery přidaly pomocí on<event> -property nebo pomocí atributů HTML nebo pomocí dvouargumentů addEventListener(event, handler) nevědí nic o snímání, běží pouze ve 2. a 3. fázi.

Abychom mohli zachytit událost ve fázi zachycování, musíme nastavit handler capture možnost true :

elem.addEventListener(..., {capture: true})

// or, just "true" is an alias to {capture: true}
elem.addEventListener(..., true)

Existují dvě možné hodnoty capture možnost:

  • Pokud je to false (výchozí), pak je handler nastaven na fázi probublávání.
  • Pokud je to true , pak je handler nastaven na fázi zachycení.

Všimněte si, že i když formálně existují 3 fáze, 2. fáze („cílová fáze“:událost dosáhla prvku) není zpracovávána samostatně:v této fázi se spouštějí obslužné programy ve fázi zachycení i probublávání.

Podívejme se jak zachycovat, tak probublávat v akci:

<style>
 body * {
 margin: 10px;
 border: 1px solid blue;
 }
</style>

<form>FORM
 <div>DIV
 <p>P</p>
 </div>
</form>

<script>
 for(let elem of document.querySelectorAll('*')) {
 elem.addEventListener("click", e => alert(`Capturing: ${elem.tagName}`), true);
 elem.addEventListener("click", e => alert(`Bubbling: ${elem.tagName}`));
 }
</script>

Kód nastavuje obslužné nástroje kliknutí na každý prvek v dokumentu, abyste viděli, které z nich fungují.

Pokud kliknete na <p> , pak sekvence je:

  1. HTMLBODYFORMDIV -> P (fáze zachycení, první posluchač):
  2. PDIVFORMBODYHTML (fáze bublání, druhý posluchač).

Všimněte si prosím P se zobrazí dvakrát, protože jsme nastavili dva posluchače:zachycující a bublající. Cíl se spustí na konci první a na začátku druhé fáze.

Existuje vlastnost event.eventPhase to nám říká číslo fáze, ve které byla událost zachycena. Používá se ale zřídka, protože to obvykle známe v handleru.

Chcete-li ovladač odebrat, removeEventListener potřebuje stejnou fázi

Pokud addEventListener(..., true) , pak bychom měli zmínit stejnou fázi v removeEventListener(..., true) správně odstranit ovladač.

Posluchači na stejném prvku a stejné fázi běží ve svém nastaveném pořadí

Pokud máme ve stejné fázi více obslužných programů událostí, přiřazených ke stejnému prvku s addEventListener , běží ve stejném pořadí, v jakém byly vytvořeny:

elem.addEventListener("click", e => alert(1)); // guaranteed to trigger first
elem.addEventListener("click", e => alert(2));
event.stopPropagation() při zachycení také zabraňuje bublání

event.stopPropagation() metoda a její sourozenec event.stopImmediatePropagation() lze také vyvolat ve fázi zachycení. Pak se zastaví nejen další snímání, ale i bublání.

Jinými slovy, normálně událost jde nejprve dolů („zachycení“) a poté nahoru („bublání“). Ale pokud event.stopPropagation() je volána během fáze zachycování, pak se pohyb události zastaví a nedojde k žádnému bublání.

Shrnutí

Když dojde k události – nejvíce vnořený prvek, kde k ní dojde, je označen jako „cílový prvek“ (event.target ).

  • Poté se událost přesune z kořenového adresáře dokumentu dolů na event.target , volající obslužné nástroje přiřazené addEventListener(..., true) na cestě (true je zkratka pro {capture: true} ).
  • Poté jsou volány handlery na samotný cílový prvek.
  • Poté událost vybuchne od event.target do kořene, volající obslužné rutiny přiřazené pomocí on<event> , atributy HTML a addEventListener bez 3. argumentu nebo s 3. argumentem false/{capture:false} .

Každý handler má přístup k event vlastnosti objektu:

  • event.target – nejhlubší prvek, který událost inicioval.
  • event.currentTarget (=this ) – aktuální prvek, který zpracovává událost (ten, na kterém je handler)
  • event.eventPhase – aktuální fáze (zachycení=1, cíl=2, bublání=3).

Jakákoli obsluha události může událost zastavit voláním event.stopPropagation() , ale to se nedoporučuje, protože si nemůžeme být jisti, že to výše nebudeme potřebovat, možná pro úplně jiné věci.

Fáze zachycení se používá velmi zřídka, obvykle řešíme události na bublání. A existuje pro to logické vysvětlení.

V reálném světě, když dojde k nehodě, místní úřady reagují jako první. Ti nejlépe znají oblast, kde se to stalo. V případě potřeby pak orgány vyšší úrovně.

Totéž pro obsluhu událostí. Kód, který nastavuje obslužnou rutinu na konkrétní prvek, zná maximum podrobností o prvku a o tom, co dělá. Obslužná rutina na konkrétním <td> může být vhodný právě pro to <td> , ví o tom všechno, takže by měla dostat šanci jako první. Jeho bezprostřední rodič také ví o kontextu, ale o něco méně, a tak dále až do úplně nejvyššího prvku, který zpracovává obecné pojmy a spouští ten poslední.

Probublávání a zachycování položí základ pro „delegování událostí“ – extrémně účinný model zpracování událostí, který studujeme v další kapitole.