Beaucoup dâévènements mènent à lâexécution dâactions par le navigateur.
Par exemple:
- Un clic sur un lien â initie la navigation vers son URL.
- Un clic sur un bouton dâenvoi de formulaire â initie son envoie vers le serveur.
- Appuyer sur un bouton de la souris au-dessus dâun texte et la déplacer â sélectionne le texte.
Si nous gérons un évènement avec JavaScript, nous pouvons ne pas avoir envie de déclencher lâaction de navigateur associée, et déclencher un autre comportement à la place.
Empêcher les actions du navigateur
Il y a deux manières de dire au navigateur que nous ne souhaitons pas quâil agisse:
- La manière principale est dâutiliser lâobjet
event. Il y a une méthodeevent.preventDefault(). - Si le gestionnaire dâévènement a été assigné en utilisant
on<event>(pas paraddEventListener), alors renvoyerfalsefonctionne de la même manière.
Dans cet HTML un clic sur un lien nâentraine pas une navigation, le navigateur ne fait rien :
<a href="/" onclick="return false">Cliquez ici</a>
ou
<a href="/" onclick="event.preventDefault()">ici</a>
Dans le prochain exemple nous allons utiliser cette technique pour créer un menu avec JavaScript.
false depuis un gestionnaire dâévènement est une exceptionLa valeur renvoyée par un gestionnaire dâévènement est généralement ignorée.
La seule exception est return false depuis un gestionnaire assigné en utilisant on<event>.
Dans tous les autres cas, la valeur de return est ignorée. Il nây a aucune raison de renvoyer true.
Exemple: le menu
Considérez le menu dâun site, comme ceci:
<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>
Voici à quoi il ressemble avec un peu de CSS:
Les objets du menu sont implémentés comme des liens HTML <a>, pas des boutons <button>. Il y a plusieurs raisons pour ceci, par exemple:
- Beaucoup de gens aiment utiliser âclic droitâ â âouvrir dans une nouvelle fenêtreâ. Si nous utilisons
<button>ou<span>, cela ne fonctionne pas. - Les moteurs de recherche suivent les liens
<a href="...">lors de lâindexation.
Donc nous utilisons <a>. Mais normalement nous avons lâintention de gérer les clics avec JavaScript. Donc nous devrions empêcher les actions par défaut du navigateur.
Comme ici:
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
let href = event.target.getAttribute('href');
alert( href ); // ...peut être en chargement depuis le serveur, génération d'UI etc
return false; // empêche l'action du navigateur (ne va pas sur l'URL)
};
Si nous enlevons return false, alors après lâexécution de notre code le navigateur fera son âaction par défautâ â naviguer vers lâURL dans href. Et nous nâavons pas besoin de ça ici comme nous gérons nous-mêmes les clics.
Au passage, utiliser la délégation dâévènement ici rend notre menu très flexible. Nous pouvons ajouter des listes imbriquées et les styliser en utilisant CSS pour âles faire glisserâ.
Certains évènements se suivent les uns après les autres. Si nous empêchons le premier évènement, il nây aura pas de second.
Par exemple, mousedown sur un champ <input> entraine un focus dessus, et lâévènement focus. Si nous empêchons lâévènement mousedown, il nây a pas de focus.
Essayez de cliquer sur le premier <input> ci-dessous â lâévènement focus se produit. Mais si vous cliquez sur le second, il nây a pas de focus.
<input value="Focus fonctionne" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Cliquez sur moi">
Câest parce que lâaction du navigateur est annulée lors de mousedown. Le focus est toujours possible si nous utilisons une autre manière dâentrer dans le champ de saisie. Par exemple, la touche Tab pour passer du premier champ au deuxième. Mais plus avec le clic de la souris.
Lâoption de gestionnaire âpassiveâ
Lâoption facultative passive: true de addEventListener signale au navigateur que ce gestionnaire nâappellera pas preventDefault().
Pourquoi cela pourrait-il être nécessaire ?
Il y a certains évènements comme touchmove sur les appareils mobile (lorsque lâutilisateur déplace ses doigts sur lâécran), qui entrainent un défilement par défaut, mais ce défilement peut être empêché en utilisant preventDefault() dans le gestionnaire.
Donc lorsque le navigateur detecte un tel évènement, il doit dâabord traiter tous les gestionnaires, et si preventDefault nâest appelé nulle part, il peut continuer avec le défilement. Cela peut causer des délais et âtremblementsâ inutile dans lâUI.
Lâoption passive: true communique au navigateur que le gestionnaire nâannulera pas le défilement. Alors le navigateur défile immédiatement, fournissant ainsi une expérience fluide au maximum, et lâévènement est géré au passage.
Pour certains navigateurs (Firefox, Chrome), passive est true par défaut pour les évènements touchstart et touchmove.
event.defaultPrevented
La propriété event.defaultPrevented est true si lâaction par défaut a été empêchée, et false dans les autres cas.
Il y a un cas dâutilisation intéressant avec ceci.
Dans le chapitre Bubbling and capturing nous avons parlé de event.stopPropagation() et pourquoi arrêter le bubbling est mauvais?
Parfois nous pouvons utiliser event.defaultPrevented à la place, pour signaler aux autres gestionnaires dâévènement que lâévènement a été géré.
Voyons un exemple pratique.
Par défaut le navigateur affiche un menu contextuel avec des options standards lors de lâévènement contextmenu (clic droit). Nous pouvons empêcher cela et afficher le nôtre comme ceci:
<button>Clic droit affiche le menu contextuel du navigateur</button>
<button oncontextmenu="alert('Affiche notre menu'); return false">
Clic droit affiche notre menu contextuel
</button>
Maintenant, en plus de ce menu contextuel nous voulons implémenter un menu contextuel sur tout le document.
Après un clic droit, le menu contextuel le plus proche devrait sâafficher.
<p>Clic droit ici pour le menu contextuel du document</p>
<button id="elem">Clic droit ici pour le menu contextuel du bouton</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Menu contextuel du bouton");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Menu contextuel du document");
};
</script>
Le problème est que lorsque lâon clique sur elem, nous obtenons deux menus: le menu du bouton et (lâévènement remonte) le menu du document.
Comment régler cela? Une des solutions est de penser: âQuand nous gérons le clic droit dans le gestionnaire du bouton, arrêtons sa propagationâ et utilisons event.stopPropagation():
<p>Clic droit ici pour le menu contextuel du document</p>
<button id="elem">Clic droit ici pour le menu contextuel du bouton (réparé avec event.stopPropagation)</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
event.stopPropagation();
alert("Menu contextuel du bouton");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Menu contextuel du document");
};
</script>
Maintenant le bouton contextuel du menu fonctionne comme voulu. Mais le prix est elevé. Nous bloquons pour toujours lâaccès aux informations sur les clics droits au code extérieur, comme les compteurs qui récoltent des statistiques. Ce nâest pas sage.
Une solution alternative est de vérifier dans le gestionnaire du document si lâaction par défaut a été empêchée? Si câest le cas, alors lâévènement a été géré, et nous nâavons pas besoin de réagir.
<p>Clic droit ici pour le menu contextuel du document (une vérification a été ajoutée avec event.defaultPrevented)</p>
<button id="elem">Clic droit ici pour le menu contextuel du bouton</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Menu contextuel du bouton");
};
document.oncontextmenu = function(event) {
if (event.defaultPrevented) return;
event.preventDefault();
alert("Menu contextuel du document");
};
</script>
Maintenant tout fonctionne correctement. Si nous avons des éléments imbriqués, et que chacun dâeux possède son propre menu contextuel, cela fonctionnerait aussi. Assurez-vous juste de vérifier event.defaultPrevented dans chaque gestionnaire contextmenu.
Comme nous pouvons le voir, event.stopPropagation() et event.preventDefault() (aussi connu comme return false) sont deux choses différentes. Ils nâont pas liens.
Il y a aussi des manières alternatives dâimplémenter des menus contextuels imbriqués. Une dâelle est dâavoir un seul objet global avec un gestionnaire pour document.oncontextmenu, et dâautre méthodes pour nous permettre de stocker dâautres gestionnaires dedans.
Lâobjet captura tous les clics droit, et lancera le gestionnaire approprié parmi les gestionnaires stockés.
Mais chaque partie du code qui nécessite un menu contextuel devra avoir connaissance de cet objet et lâutiliser plutôt que le gestionnaire contextmenu.
Résumé
Il y a plusieurs actions de navigateur par défaut:
mousedownâ débute la sélection (déplacer la souris pour sélectionner).clicksur<input type="checkbox">â coche/décoche leinput.submitâ ciquer sur un<input type="submit">oou appuyer sur Enter à lâintérieur dâun formulaire entraine le lancement de cet évènement, et le navigateur envoie le formulaire après.keydownâ appuyer sur une touche peut amener à ajouter un caractère dans un champ, ou dâautres actions.contextmenuâ lâévènement se déclenche lors dâun clic droit, lâaction est dâafficher le menu contextuel du navigateur.- â¦il y en a plusâ¦
Toutes les actions par défaut peuvent être empêchées si nous voulons gérer lâévènement exclusivement avec JavaScript.
Pour empêcher une action par défaut â utilisez soit event.preventDefault(), soit return false. La seconde méthode ne fonctionne que pour les gestionnaires assignés avec on<event>.
Lâoption passive: true de addEventListener dis au navigateur que lâaction ne va pas être empêchée. Câest utile pour certains évènements de mobile, comme touchstart et touchmove, pour dire au navigateur quâil ne devrait pas attendre que tous les gestionnaires soient terminés pour défiler la page.
Si lâaction par défaut a été empêchée, la valeur de event.defaultPrevented devient true, sinon false.
Techniquement, en empêchant les actions par défaut et en ajoutant du JavaScript nous pouvons personnaliser le comportement de nâimporte quel élément. Par exemple, nous pouvons faire fonctionner un lien <a> comme un bouton, et un bouton <button> se comporter comme un lien (rediriger vers une autre URL ou autre).
Mais nous devrions généralement garder la signification sémantique des éléments HTML. Par exemple <a> devrait entrainer une navigation, pas un bouton.
Ce nâest pas âjuste une bonne choseâ, cela rend votre HTML meilleur en terme dâaccessibilité.
Aussi, si nous prenons lâexemple avec <a>, veuillez noter: un navigateur nous permet dâouvrir de tels liens dans une nouvelle fenêtre (en faisant un clic droit dessus par exemple). Et les gens aiment ça. Mais si nous faisons un bouton qui se comporte comme un lien en utilisant JavaScript et qui ressemble à un lien en utilisant CSS, les fonctionnalités de navigateur spécifiques à <a> ne fonctionneront toujours pas.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâ¦)