Comment gérer les raccourcis clavier en JavaScript

Nous allons voir dans cet article comment créer rapidement un système permettant de surveiller si une touche du clavier est enfoncée lors de l'exécution d'une action dans votre navigateur ou mieux, si une série de touche est enfoncée pour autoriser/lancer l'exécution d'une action lors de la pression, par exemple, sur « Ctrl + Alt + E ».

Ctrl + Alt + E
Image originale ici

Et comme toujours, on va devoir jouer avec un peu de JavaScript ! Si vous n'avez pas trop le temps de jouer, le code qui vous intéresse probablement se trouve ici

Détecter qu'une ou plusieurs touches sont enfoncées

Nous allons implémenter dans cette partie la fonctionnalité tant convoitée, mais de différentes manières. Cela vous ferra un exercice JavaScript intéressant. Par exemple sur cette page, appuyez sur « Ctrl + Alt + E ».

Avec les méthodes JavaScript inline onkeydown et onkeyup

Pour les anciens, vous les connaissez peut-être pour les avoir croisés au détour d'un attribut HTML comme ici : <input type="text" onkeydown="doSomething()" />. Ce qu'il faut retenir de la méthode inline pour les événements, c'est qu'elle ne garde que la dernière affectation de code, et qu'elle écrase toutes celles associées précédemment. Voyons plus bas cette implémentation.

Tester cet exemple de code en live sur cette page.

/*
 * Nous sommes dans le scope global,
 * aussi l'objet `keys` est accessible partout
 * dans le code à travers tous les fichiers JavaScript.
*/
var keys = {};

/*
 * Étant dans le code global,
 * `window.onkeydown` est identique à `this.onkeydown` 
 * lui même identique à `onkeydown`.
 * On associe ci-dessous la même fonction lorsqu'une
 * touche est appuyée, et lorsqu'une touche est relachée.
*/
onkeydown = onkeyup = function (e) {

    /*
     * Si `e` n'existe pas, 
     * nous somme probablement dans un vieux IE.
     * On affecte alors `event` à `e`.
     */
    e = e || event;

    /*
     * Si la fonction courante est executée, 
     * quand une touche est enfoncée,
     * `e.type === 'keydown'` renverra `true`
     * sinon elle renverra `false`.
     * Il suffit alors d'assigner chaque état 
     * dans le tableau `keys` pour chaque
     * touche `e.keyCode`.
     */     
    keys[e.keyCode] = e.type === 'keydown';

    /*
     * Cette zone sera exécutée lorsque les touches
     * Ctrl (17), Alt (18) et E (69)
     * seront enfoncée en même temps
     * car l'objet `keys` vaudra alors :
     * {
     *  17: true,
     *  18: true,
     *  69: true
     * }
       */
    if (keys[17] && keys[18] && keys[69]) {

      /*
       * Affichera dans la console (F12, onglet console)
       * le texte « Ctrl + Alt + E ».
       */
      console.log('Ctrl + Alt + E');
    }
}

/*
 * Si l'on clique dans le navigateur...
 */
onclick = function () {

    /*
     * ...alors que les touches
     * Ctrl (17), Alt (16) et E (69)
     * sont appuyées...
     */
    if (keys[17] && keys[16] && keys[69]) {

        /*
         * ...on affichera dans la console 
         * le texte « Ctrl + Alt + E ».
         */
        console.log('Ctrl + Shift + E');
    }
}

Avec les écouteurs d'événement JavaScript

Étant donné que nous accrochons nos événements à l'objet global window, le mieux est d'abonner les événements à des écouteurs plutôt que d'utiliser les méthodes inline, comme cela nous pourrons par la suite abonner d'autres fonctions.

Cela se fait avec addEventListener (ou attachEvent sur les anciens IE).

Tester cet exemple de code en live sur cette page.

var keys = {};

/*
 * Mise du code appelé en commun dans une
 * fonction que nous allons
 * abonnée à un écouteur d'événement.
 */
function trackMultipleKeyStroke (e) {
    e = e || event;
    keys[e.keyCode] = e.type === 'keydown';

    /*
     * Cette partie constitue le code exécuté quand
     * Ctrl (17), Alt (18) et E (69)
     * sont enfoncées.
     */
    if (keys[17] && keys[18] && keys[69]) {
        console.log('Ctrl + Alt + E');
    }
}

/*
 * Fonction de rétro-compatibilité pour
 * les navigateurs Internet Explorer.
 * Elle marchera dans tous les navigateurs
 * et demandera qui s'abonne, à quel événement
 * et ce qu'il se passe quand l'événement est
 * appelé/levé.
 */
function addEvent(element, event, func) {

    /*
     * Avons nous à faire à un vieil Internet Explorer ?
     */
    if (element.attachEvent) {

        /*
         * Abonnons nous alors comme Internet Explorer le propose.
         */
        return element.attachEvent('on' + event, func);
    } else {

          /*
           * Nous nous abonnons comme la spécification ECMAScript le propose.
           */
        return element.addEventListener(event, func, false);
      }
}

/*
 * Appel de la fonction de rétro-compatibilité 
 * que nous venons de créer pour abonner `window`
 * au événement `keydown` et `keyup`
 */
addEvent(window, "keydown", trackMultipleKeyStroke);
addEvent(window, "keyup", trackMultipleKeyStroke);

/*
 * Cette partie constitue le code exécuté quand
 * on clique dans la page...
 */
addEvent(window, "click", function () {

    /*
     * ...si Ctrl (17), Shift (16) et E (69)
     * sont enfoncées.
     */
    if (keys[17] && keys[16] && keys[69]) {
        console.log('Ctrl + Shift + E');
    }
});

Avec jQuery

Comme toujours, pour ne pas vous soucier de la compatibilité et écrire moins de code, vous pouvez faire appel à la librairie jQuery. Comme le code proposé ci-dessous.

Tester cet exemple de code en live sur cette page.

var keys = {};

$(window).on("keyup keydown", function (e) {
    e = e || event;
    keys[e.keyCode] = e.type === 'keydown';

    /*
     * Cette partie constitue le code exécuté quand
     * Ctrl (17), Alt (18) et E (69)
     * sont enfoncées.
     */
    if (keys[17] && keys[18] && keys[69]) {
        console.log('Ctrl + Alt + E');
    }

    /*
     * Cette partie constitue le code exécuté quand
     * on clique dans la page...
     */
}).click(function () {

    /*
     * ...si Ctrl (17), Shift (16) et E (69)
     * sont enfoncées.
     */
    if (keys[17] && keys[16] && keys[69]) {
        console.log('Ctrl + Shift + E');
    }
});

3 points à savoir

Il y a quelques points à savoir pour ensuite utiliser ces raccourcis clavier en toutes circonstances.

  1. Perte de focus

    Quand vous levez une popup via alert ou plus globalement que vous perdez le focus, l'événement keyup n'est pas levé ce qui laisse penser au navigateur que vous maintenez encore les touches alors que ce n'est peut-être plus le cas. Pour remédier à cela, l'astuce est de remettre à vide l'objet global keys.

    Ainsi le code ci-dessous produit le bug : voyez-plutôt

    var keys = {};
    $(window).on("keyup keydown", function (e) {
     e = e || event;
     keys[e.keyCode] = e.type === 'keydown';
    }).click(function () {
     /*
      * Quand vous ré-appuierez sur une des trois touches de la combinaison,
      * même sans appuyer sur les autres touches,
      * l'alerte se relancera car elle empêche les valeurs de l'objet 
      * contenant les touches de repasser à `false` quand vous les relâchés.
      */
     if (keys[17] && keys[16] && keys[69]) {
         alert('Ctrl + Shift + E');
     }
    });
    

    La solution est donc de vider toutes les valeurs pour qu'elles soient interprétées comme false après l'appel de la fonction faisant perdre le focus.

    Ainsi le code ci-dessous résout le bug : voyez-plutôt

    var keys = {};
    $(window).on("keyup keydown", function (e) {
     e = e || event;
     keys[e.keyCode] = e.type === 'keydown';
    }).click(function () {
     if (keys[17] && keys[16] && keys[69]) {
         alert('Ctrl + Shift + E');
         /*
          * Remet les valeurs des touches à `false` en les vidants.
          */
         keys = {};
     }
    });
    
  2. Plusieurs écoutes, bon ordre

    Si vous souhaiter lister vos raccourcis clavier, il faut lister vos combinaisons de touches de la séquence la plus longue à la séquence la plus courte avec des else if sinon vous n’arriverez jamais à faire fonctionner les combinaisons les plus longues.

    Ci-dessous la seconde combinaison ne sera jamais appelée : voyez-plutôt

    if (keys[17] && keys[69]) {
     console.log('Ctrl + E');
    } else if (keys[17] && keys[16] && keys[69]) {
     console.log('Ctrl + Shift + E');
    }
    

    La bonne pratique est donc la suivante.

    Ci-dessous la seconde combinaison fonctionnera : voyez-plutôt

    if (keys[17] && keys[16] && keys[69]) {
     console.log('Ctrl + Shift + E');
    } else if (keys[17] && keys[69]) {
     console.log('Ctrl + E');
    }
    
  3. Prendre la main sur les raccourcis du navigateur

    Parfois, votre combinaison de touche ouvre une action par défaut programmée par le navigateur. Si vous souhaitez l'annuler, faites comme suit :

    var keys = {};
    $(window).on("keyup keydown", function (e) {
     e = e || event;
     keys[e.keyCode] = e.type === 'keydown';
    
     if (keys[17]] && keys[84]) {
    
         /*
          * Normalement, un nouvel onglet devrait s'ouvrir, 
          * car c'est le raccourci utilisé pour cela.
          */
         console.log('Ctrl + T');
    
         /*
          * Mais pas en ajoutant `return false`.
          */
         return false;
     }
    });
    

Les Char Codes ou Key Codes

Vous trouverez la liste des nombres à mettre dans votre objet keys sur la page suivante.