18 octobre 2023

Le Clavier: les évènements keydown et keyup

Avant que nous en arrivions au clavier, veuillez noter que sur des appareils modernes il y a d’autres manières de “récupérer quelque chose". Par exemple, les gens utilisent la reconnaissance vocale (en particulier sur les appareils mobiles) oubien le copier/coller avec la souris.

Donc si nous voulons contrôler une entrée dans un champ <input>, alors les évènements du clavier ne sont pas assez suffisants. Il y a un autre évènement nommé input pour gérer les changements d’un champ <input>, par n’importe quelle moyen. Et il peut être un meilleur choix pour une telle tâche. Nous allons traiter cela plus tard dans le chapitre Les événements: change, input, cut, copy, paste.

Les évènements du clavier doivent être utilisés lorsqu’on veut gérer les actions sur le clavier (Le clavier virtuel compte aussi). Par exemple, pour réagir sur les touches de directions Up et Down oubien les touches de raccourcis (y compris les combinaisons de touches).

Teststand

Pour mieux comprendre les évènements du clavier, vous pouvez utiliser le teststand en bas.

Essayez les différentes combinaisons de touches dans la zone de texte.

Résultat
script.js
style.css
index.html
kinput.onkeydown = kinput.onkeyup = kinput.onkeypress = handle;

let lastTime = Date.now();

function handle(e) {
  if (form.elements[e.type + 'Ignore'].checked) return;

  area.scrollTop = 1e6;

  let text = e.type +
    ' key=' + e.key +
    ' code=' + e.code +
    (e.shiftKey ? ' shiftKey' : '') +
    (e.ctrlKey ? ' ctrlKey' : '') +
    (e.altKey ? ' altKey' : '') +
    (e.metaKey ? ' metaKey' : '') +
    (e.repeat ? ' (repeat)' : '') +
    "\n";

  if (area.value && Date.now() - lastTime > 250) {
    area.value += new Array(81).join('-') + '\n';
  }
  lastTime = Date.now();

  area.value += text;

  if (form.elements[e.type + 'Stop'].checked) {
    e.preventDefault();
  }
}
#kinput {
  font-size: 150%;
  box-sizing: border-box;
  width: 95%;
}

#area {
  width: 95%;
  box-sizing: border-box;
  height: 250px;
  border: 1px solid black;
  display: block;
}

form label {
  display: inline;
  white-space: nowrap;
}
<!DOCTYPE HTML>
<html>

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

<body>

  <form id="form" onsubmit="return false">

    Prevent default for:
    <label>
      <input type="checkbox" name="keydownStop" value="1"> keydown</label>&nbsp;&nbsp;&nbsp;
    <label>
      <input type="checkbox" name="keyupStop" value="1"> keyup</label>

    <p>
      Ignore:
      <label>
        <input type="checkbox" name="keydownIgnore" value="1"> keydown</label>&nbsp;&nbsp;&nbsp;
      <label>
        <input type="checkbox" name="keyupIgnore" value="1"> keyup</label>
    </p>

    <p>Mettre le focus sur le champ d'entrée et appuyer une touche.</p>

    <input type="text" placeholder="Press keys here" id="kinput">

    <textarea id="area" readonly></textarea>
    <input type="button" value="Clear" onclick="area.value = ''" />
  </form>
  <script src="script.js"></script>


</body>
</html>

Keydown et keyup

Les évènements keydown surviennent lorsqu’une touche est appuyée, et ensuite intervient keyup – lorsqu’elle est relâchée.

event.code et event.key

La propriété key de l’objet évènement permet d’obtenir un caractère, tandis que la propriété code de l’objet évènement objet permet d’obtenir le “code de la touche physique”.

Par exemple, la même touche Z peut être appuyée avec ou sans Shift. Cela nous donne deux caractères différents : minuscule z et majuscule Z.

La propriété event.key est exactement le caractère, et il sera diffèrent. Cependant event.code est la même:

Touche event.key event.code
Z z (minuscule) KeyZ
Shift+Z Z (majuscule) KeyZ

Si un utilisateur travaille avec des langues différentes, alors le fait de changer vers une autre langue aura pour effet de créer un caractère totalement diffèrent de "Z". Cela va devenir la valeur de event.key, tandis que event.code est toujours la même que: "KeyZ".

“KeyZ” et autres codes de touches

Chaque touche a un code qui dépend de sa position sur le clavier. Les codes des touches sont décrits dans le document Spécification des codes des évènements Interfaces Utilisateurs.

Par exemple:

  • Les touches des lettres ont des codes de type: "Key<letter>": "KeyA", "KeyB" etc.
  • Les touches numériques ont des codes de type: "Digit<number>": "Digit0", "Digit1" etc.
  • Les touches spéciales sont codées par leurs noms: "Enter", "Backspace", "Tab" etc.

Il existe plusieurs formats de claviers usuels, différents de par la présentation, et la spécification donne des codes pour les touches pour chacun d’entre eux.

voir la section alphanumérique de la specification pour plus de codes, ou essayez juste le teststand au-dessus.

Le cas importe: "KeyZ", pas "keyZ"

Semble être évident, mais beaucoup de gens font toujours des fautes.

S’il vous plait éviter les fautes de frappes: c’est KeyZ, pas keyZ. Le control tel que event.code=="keyZ" ne vas pas fonctionner: la première lettre de "Key" doit être une majuscule.

Et si une touche ne donne aucun caractère ? Par exemple, Shift ou F1 ou autres. Pour ces touches, event.key est approximativement la même chose que event.code :

Key event.key event.code
F1 F1 F1
Backspace Backspace Backspace
Shift Shift ShiftRight or ShiftLeft

Veuillez noter que event.code spécifie exactement quelle touche est appuyée. Par exemple, la plupart des claviers ont deux touches de Shift: à gauche et à droite. La propriété event.code nous dit exactement laquelle fut appuyée, et event.key est responsable de la " signification " de la touche: comment il s’agit d’un (“Shift”).

Disons, nous voulons gérer un raccourci : Ctrl+Z (ou Cmd+Z pour Mac). La plupart des éditeurs accrochent l’action du “Defaire” sur cette touche. Nous pouvons mettre un écouteur lorsqu’on déclenche l’évènement keydown et chercher à savoir quelle touche est appuyée.

Il existe un dilemme ici: Dans cet écouteur d’évènement, devons-nous contrôler la valeur de event.key oubien event.code?

D’une part, la valeur de event.key est un caractère, elle change en fonction de la langue. Si le visiteur a plusieurs langues dans le système d’exploitation et bascule entre elles, la même clé donne des caractères différents. Il est donc logique de vérifier event.code, c’est toujours pareil.

Ainsi:

document.addEventListener('keydown', function(event) {
  if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) {
    alert('Undo!')
  }
});

D’autre part, il existe un problème avec event.code. Pour des dispositions de clavier différentes, la même touche peut avoir des caractères différents.

Par exemple, voici la disposition du clavier Américain (“QWERTY”) et Allemand (“QWERTZ”) dessous (de Wikipedia) :

Pour la même touche, le clavier Américain a un “Z”, tandis que celui Allemand a un “Y” (les lettres sont échanges).

Donc, event.code sera égal à KeyZ pour les gens utilisant le clavier Allemand lorsqu’ils appuient sur Y.

Si on vérifie event.code == 'KeyZ' dans notre code, alors pour les personnes avec la disposition allemande ce genre de test passera quand ils appuient sur Y.

Cela semble être bizarre, mais c’est ainsi. La specification mentionne explicitement ce comportement.

Donc, event.code peut correspondre à un caractère incorrect pour une disposition inattendue. Les mêmes lettres dans différentes disposition peuvent mapper sur différentes clés physiques, conduisant à des codes différents. Heureusement, cela ne se produit qu’avec plusieurs codes, par exemple keyA, keyQ, keyZ (comme nous l’avons vu), et ne se produit pas avec des touches spéciales telles que Shift. Vous pouvez trouver la liste dans la specification.

Pour suivre de manière fiable les caractères dépendant de la disposition, event.key peut être un meilleur moyen.

D’un autre côté, event.code a l’avantage de rester toujours le même, lié à l’emplacement de la clé physique. Ainsi, les raccourcis clavier qui en dépendent fonctionnent bien même en cas de changement de langue.

Voulons-nous gérer des clés dépendantes de la disposition ? Alors event.key est la voie à suivre.

Ou voulons-nous un raccourci clavier même après un changement de langue ? Alors, event.code peut être une meilleure option.

Auto-repeat

Si une touche est appuyée assez longtemps, elle commence la répétition avec la propriété “auto-repeat”: l’évènement keydown se déclenche de manière répétitive, et ensuite lorsqu’elle est relâchée nous obtenons finalement l’évènement keyup. Donc c’est normal d’avoir plusieurs keydown et un unique évènement keyup.

Pour les évènements déclenchés par auto-repeat, l’évènement objet a une propriété event.repeat dont la valeur est assignée à true.

Actions par défaut

Les actions par défaut varient, comme il y a beaucoup de possibilités pouvant être initiées par le clavier.

Pa exemple:

  • Un caractère apparait sur l’écran (le résultat le plus évident).
  • Un caractère est supprimé (Delete key).
  • Une page est défilée (PageDown key).
  • Le navigateur ouvre la boite de dialogue “Sauvegarder la Page” dialog (Ctrl+S)
  • …ainsi de suite.

L’empêchement de l’action par défaut à l’évènement keydown peut annuler la plupart d’entre elles, à l’exception des touches spéciales spécifiques aux systèmes d’exploitations. Par exemple, sur Windows Alt+F4 ferme la fenêtre en cours du navigateur. Et il n’existe aucun moyen de l’arrêter en empêchant l’action par défaut JavaScript.

Par exemple, la balise <input> en bas s’attend à recevoir un numéro de téléphone, alors elle n’accepte que les touches numériques, +, () oubien -:

<script>
function checkPhoneKey(key) {
  return (key >= '0' && key <= '9') || ['+','(',')','-'].includes(key);
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">

Le gestionnaire onkeydown utilise ici checkPhoneKey pour vérifier la touche enfoncée. S’il est valide (de 0..9 ou l’un des +-()), alors il renvoie true, sinon false.

Comme nous le savons, la valeur false renvoyée par le gestionnaire d’événements, attribuée à l’aide d’une propriété DOM ou d’un attribut, comme ci-dessus, empêche l’action par défaut, donc rien n’apparaît dans le <input> pour les clés qui ne passent pas le test. (La valeur ‘true’ renvoyée n’affecte rien, seul le renvoi de ‘false’ compte)

Veuillez noter que les touches spéciales, telles que Backspace, Left, Right, ne fonctionnent pas dans l’input. C’est un effet secondaire du filtre strict checkPhoneKey. Ces clés lui font retourner false.

Détendons un peu le filtre en autorisant les touches fléchées Left, Right et Delete, Backspace :

<script>
function checkPhoneKey(key) {
  return (key >= '0' && key <= '9') ||
    ['+','(',')','-','ArrowLeft','ArrowRight','Delete','Backspace'].includes(key);
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">

maintenant les flèches et la suppression marchent bien.

Même si nous avons le filtre de clavier, on peut toujours entrer n’importe quoi à l’aide d’une souris et faire un clic droit + Coller. Les appareils mobiles offrent d’autres moyens de saisir des valeurs. Le filtre n’est donc pas fiable à 100%.

L’approche alternative serait de suivre l’événement oninput – il se déclenche après toute modification. Là, nous pouvons vérifier le nouveau input.value et le modifier/mettre en surbrillance le <input> lorsqu’il est invalide. Ou nous pouvons utiliser les deux gestionnaires d’événements ensemble.

Héritage

Dans le passé, il y’avait un évènement keypress, et aussi les propriétés keyCode, charCode, which de l’objet évènement.

Il y avait tellement d’incompatibilités au niveau des navigateurs en travaillant avec eux que les développeurs de la spécification n’avaient autre moyen que de les déprécier tous et d’en créer de nouveaux et plus modernes (tels que ceux décrits en haut dans ce chapitre). L’ancien code marche encore, étant donné que les navigateurs continuent de les supporter, mais nous n’avons nullement besoin de les utiliser maintenant.

Claviers mobiles

Lors de l’utilisation de claviers virtuels / mobiles, officiellement appelés IME (Input-Method Editor), la norme W3C indique qu’un KeyboardEvent e.keyCode devrait être 229 et e.key devrait être "Unidentified".

Bien que certains de ces claviers puissent toujours utiliser les bonnes valeurs pour e.key, e.code, e.keyCode, … lorsque vous appuyez sur certaines touches telles que les flèches ou le retour arrière, il n’y a aucune garantie, donc la logique de votre clavier peut ne pas toujours fonctionner sur les appareils mobiles.

Résumé

Le fait d’appuyer sur une touche génère toujours un évènement du clavier, que cela soit une touche de symbole ou des touches spéciales telles que Shift oubien Ctrl et ainsi de suite. La seule exception est la touche Fn qui est dès fois présente sur un clavier d’ordinateur portable. Il n’y a pas d’évènement de clavier pour cela, parce que cela est implémenté à un niveau plus bas que celui du system d’exploitation.

Les Evènements du Clavier:

  • keydown – en appuyant sur la touche (auto-repeats répète automatiquement si un appui long de la touche est effectué),
  • keyup – en relâchant la touche.

Les propriétés principales des évènements du clavier:

  • code – la propriété “key code” ("KeyA", "ArrowLeft" ainsi de suite), spécifique à la position physique de la touche sur le clavier.
  • key – le caractère ("A", "a" et ainsi de suite), pour les touches ne prenant pas en charge les caractères, telles que Esc, a souvent la même valeur que code.

Dans le passé, les évènements du clavier étaient des fois utilisés pour contrôler les entrées dans les champs de formulaires. Cela n’est pas fiable, parce que les données recueillies peuvent provenir de plusieurs sources. Nous avons les évènements input et change pour gérer toute entrée de donnée (traitée plus tard dans le chapitre Les événements: change, input, cut, copy, paste). Elles se déclenchent après toute sorte d’entrée, y compris le copier-coller oubien la reconnaissance vocale.

Nous devons utiliser les évènements du clavier quand nous voulons vraiment utiliser le clavier. Par exemple, pour réagir aux raccourcis ou touches spéciales.

Exercices

importance: 5

Créer une fonction runOnKeys(func, code1, code2, ... code_n) exécutant la fonction func lorsqu’on appuie simultanément sur les touches avec les codes suivant code1, code2, …, code_n.

Par exemple, le code ci-dessous montre alert lorsque "Q" et "W" sont appuyées ensemble (dans n’importe quelle langue, avec ou sans l’activation de La touche Majuscule, CapsLock)

runOnKeys(
  () => alert("Hello!"),
  "KeyQ",
  "KeyW"
);

Démo dans une nouvelle fenêtre

Nous devons utiliser deux gestionnaires: document.onkeydown et document.onkeyup.

L’ensemble pressed doit garder les touches en cours appuyées.

Créons un set pressed = new Set() pour garder les touches actuellement enfoncées.

Le premier gestionnaire en ajoute, tandis que le second en supprime. Chaque fois sur keydown nous vérifions si nous avons suffisamment de touches enfoncées et exécutons la fonction si c’est le cas.

Ouvrez la solution dans une sandbox.

Carte du tutoriel