Mnoho událostí automaticky vede k určitým akcím provedeným prohlížečem.
Například:
- Kliknutí na odkaz – spustí navigaci na jeho adresu URL.
- Kliknutí na tlačítko odeslání formuláře – zahájí odeslání formuláře na server.
- Stisknutím tlačítka myši nad textem a jeho posunutím – vyberete text.
Pokud zpracováváme událost v JavaScriptu, nemusíme chtít, aby došlo k odpovídající akci prohlížeče, a místo toho budeme chtít implementovat jiné chování.
Zabránění akcím prohlížeče
Existují dva způsoby, jak prohlížeči sdělit, že nechceme, aby jednal:
- Hlavním způsobem je použití
event
objekt. Existuje metodaevent.preventDefault()
. - Pokud je handler přiřazen pomocí
on<event>
(ne podleaddEventListener
), poté vrátífalse
také funguje stejně.
V tomto HTML kliknutí na odkaz nevede k navigaci; prohlížeč nic nedělá:
<a href="/" onclick="return false">Click here</a>
or
<a href="/" onclick="event.preventDefault()">here</a>
V dalším příkladu použijeme tuto techniku k vytvoření nabídky využívající JavaScript.
Vracífalse
od handlera je výjimkou Hodnota vrácená obslužnou rutinou události je obvykle ignorována.
Jedinou výjimkou je return false
z handleru přiřazeného pomocí on<event>
.
Ve všech ostatních případech return
hodnota je ignorována. Konkrétně nemá smysl vracet true
.
Příklad:nabídka
Zvažte nabídku webu, jako je tato:
<ul id="menu" class="menu">
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/css">CSS</a></li>
</ul>
Zde je návod, jak to vypadá s některými CSS:
Položky nabídky jsou implementovány jako HTML odkazy <a>
, nikoli tlačítka <button>
. Existuje několik důvodů, proč tak učinit, například:
- Mnoho lidí rádo používá „kliknutí pravým tlačítkem“ – „otevřít v novém okně“. Pokud použijeme
<button>
nebo<span>
, to nefunguje. - Vyhledávače sledují
<a href="...">
odkazy při indexování.
Použijeme tedy <a>
v označení. Ale normálně máme v úmyslu zpracovávat kliknutí v JavaScriptu. Měli bychom tedy zabránit výchozí akci prohlížeče.
Jako zde:
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
let href = event.target.getAttribute('href');
alert( href ); // ...can be loading from the server, UI generation etc
return false; // prevent browser action (don't go to the URL)
};
Pokud pomineme return false
, poté, co se náš kód spustí, prohlížeč provede svou „výchozí akci“ – přejde na adresu URL v href
. A to tady nepotřebujeme, protože klikání řešíme sami.
Mimochodem, pomocí delegování událostí zde je naše nabídka velmi flexibilní. Můžeme přidat vnořené seznamy a stylizovat je pomocí CSS, aby se „stáhly dolů“.
Navazující akceNěkteré události plynou jedna do druhé. Pokud zabráníme první události, nebude druhá.
Například mousedown
na <input>
pole vede k zaostření v něm a focus
událost. Pokud zabráníme mousedown
událost, není zaměření.
Zkuste kliknout na první <input>
níže – focus
událost se stane. Pokud však kliknete na druhý, nebude zaměření.
<input value="Focus works" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Click me">
Je to proto, že akce prohlížeče je zrušena na mousedown
. Zaostření je stále možné, pokud použijeme jiný způsob zadání vstupu. Například Tab pro přepnutí z 1. vstupu na 2. Ale už ne kliknutím myší.
Možnost „pasivního“ obslužného programu
Volitelný passive: true
možnost addEventListener
signalizuje prohlížeči, že handler nebude volat preventDefault()
.
Proč by to mohlo být potřeba?
Existují některé události jako touchmove
na mobilních zařízeních (když uživatel pohybuje prstem po obrazovce), které ve výchozím nastavení způsobují posouvání, ale tomuto posouvání lze zabránit pomocí preventDefault()
v obslužné rutině.
Takže když prohlížeč takovou událost detekuje, musí nejprve zpracovat všechny obslužné rutiny a poté, pokud preventDefault
se nikam nevolá, může pokračovat rolováním. To může způsobit zbytečné zpoždění a „chvění“ v uživatelském rozhraní.
passive: true
options sděluje prohlížeči, že handler nezruší rolování. Poté se prohlížeč okamžitě posouvá a poskytuje maximálně plynulý zážitek a událost je mimochodem zpracována.
U některých prohlížečů (Firefox, Chrome) passive
je true
ve výchozím nastavení pro touchstart
a touchmove
události.
event.defaultPrevented
Vlastnost event.defaultPrevented
je true
pokud bylo zabráněno výchozí akci, a false
jinak.
Existuje pro to zajímavý případ použití.
Pamatujete si, že v kapitole Probublávání a zachycení jsme mluvili o event.stopPropagation()
a proč je špatné zastavit bublání?
Někdy můžeme použít event.defaultPrevented
místo toho, aby signalizoval ostatním obslužným rutinám událostí, že událost byla zpracována.
Podívejme se na praktický příklad.
Ve výchozím nastavení je prohlížeč na contextmenu
událost (kliknutí pravým tlačítkem myši) zobrazí kontextové menu se standardními možnostmi. Můžeme tomu zabránit a ukázat své vlastní, takto:
<button>Right-click shows browser context menu</button>
<button oncontextmenu="alert('Draw our menu'); return false">
Right-click shows our context menu
</button>
Nyní bychom kromě této kontextové nabídky chtěli implementovat kontextovou nabídku pro celý dokument.
Po kliknutí pravým tlačítkem by se měla zobrazit nejbližší kontextová nabídka.
<p>Right-click here for the document context menu</p>
<button id="elem">Right-click here for the button context menu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Document context menu");
};
</script>
Problém je, že když klikneme na elem
, dostaneme dvě nabídky:nabídku na úrovni tlačítka a (událost se objeví) nabídku na úrovni dokumentu.
jak to opravit? Jedním z řešení je uvažovat takto:„Když zpracováváme klikání pravým tlačítkem v ovladači tlačítka, zastavme jeho bublání“ a použijte event.stopPropagation()
:
<p>Right-click for the document menu</p>
<button id="elem">Right-click for the button menu (fixed with event.stopPropagation)</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
event.stopPropagation();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Document context menu");
};
</script>
Nyní nabídka na úrovni tlačítek funguje tak, jak má. Ale cena je vysoká. Navždy zakazujeme přístup k informacím o kliknutí pravým tlačítkem pro jakýkoli vnější kód, včetně čítačů, které shromažďují statistiky a tak dále. To je docela nerozumné.
Alternativním řešením by bylo zkontrolovat document
handler, pokud bylo zabráněno výchozí akci? Pokud je to tak, událost byla zpracována a my na ni nemusíme reagovat.
<p>Right-click for the document menu (added a check for event.defaultPrevented)</p>
<button id="elem">Right-click for the button menu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
if (event.defaultPrevented) return;
event.preventDefault();
alert("Document context menu");
};
</script>
Nyní také vše funguje správně. Pokud máme vnořené prvky a každý z nich má vlastní kontextovou nabídku, fungovalo by to také. Jen nezapomeňte zkontrolovat event.defaultPrevented
v každém contextmenu
handler.
Jak jasně vidíme, event.stopPropagation()
a event.preventDefault()
(známé také jako return false
) jsou dvě různé věci. Nesouvisí spolu.
Existují také alternativní způsoby implementace vnořených kontextových nabídek. Jedním z nich je mít jeden globální objekt s handlerem pro document.oncontextmenu
, a také metody, které nám umožňují ukládat do něj další handlery.
Objekt zachytí každé kliknutí pravým tlačítkem, prohlédne si uložené ovladače a spustí příslušný.
Ale pak by každý kus kódu, který chce kontextovou nabídku, měl o tomto objektu vědět a používat jeho nápovědu místo vlastního contextmenu
handler.
Shrnutí
Existuje mnoho výchozích akcí prohlížeče:
mousedown
– spustí výběr (výběr provedete pohybem myši).click
na<input type="checkbox">
– zaškrtne/odškrtneinput
.submit
– kliknutím na<input type="submit">
nebo stisknutím klávesy Enter uvnitř pole formuláře způsobí, že k této události dojde, a prohlížeč po ní odešle formulář.keydown
– stisknutí klávesy může vést k přidání znaku do pole nebo k jiným akcím.contextmenu
– událost se stane po kliknutí pravým tlačítkem, akcí je zobrazení kontextové nabídky prohlížeče.- …je jich více…
Všem výchozím akcím lze zabránit, pokud chceme událost zpracovat výhradně pomocí JavaScriptu.
Chcete-li zabránit výchozí akci, použijte buď event.preventDefault()
nebo return false
. Druhá metoda funguje pouze pro handlery přiřazené on<event>
.
passive: true
možnost addEventListener
sděluje prohlížeči, že akci nebude zabráněno. To je užitečné pro některé mobilní události, například touchstart
a touchmove
, abyste prohlížeči řekli, že by neměl čekat na dokončení všech obslužných rutin, než bude rolovat.
Pokud bylo zabráněno výchozí akci, hodnota event.defaultPrevented
se změní na true
, jinak je to false
.
Technicky vzato, zamezením výchozích akcí a přidáním JavaScriptu můžeme přizpůsobit chování jakýchkoli prvků. Například můžeme vytvořit odkaz <a>
fungovat jako tlačítko a tlačítko <button>
chovat se jako odkaz (přesměrování na jinou adresu URL nebo tak).
Ale obecně bychom měli zachovat sémantický význam prvků HTML. Například <a>
by měl provádět navigaci, nikoli tlačítko.
Kromě toho, že je to „jen dobrá věc“, díky tomu je váš HTML lepší z hlediska přístupnosti.
Také pokud vezmeme v úvahu příklad s <a>
, pak si prosím uvědomte:prohlížeč nám umožňuje otevřít takové odkazy v novém okně (kliknutím pravým tlačítkem myši a jinými způsoby). A to se lidem líbí. Ale pokud uděláme, aby se tlačítko chovalo jako odkaz pomocí JavaScriptu a dokonce vypadalo jako odkaz pomocí CSS, pak <a>
-Specifické funkce prohlížeče pro něj stále nebudou fungovat.