Comment détecter si plusieurs touches sont pressées à la fois en utilisant JavaScript?

j'essaie de développer un moteur de jeu JavaScript et je suis tombé sur ce problème:

  • quand j'appuie sur espace le caractère saute.
  • Lorsque j'appuie sur → le personnage se déplace vers la droite.

le problème est que lorsque j'appuie à droite et que j'appuie sur space, le personnage saute et cesse de bouger.

j'utilise le Fonction keydown pour obtenir la touche pressée. Comment puis-je vérifier s'il y a plusieurs touches à la fois?

130
demandé sur Jenny O'Reilly 2011-03-05 14:07:36

9 réponses

détection de touches multiples est facile si vous comprenez le concept

La façon dont je le fais, c'est comme ceci:

var map = {}; // You could also use an array
onkeydown = onkeyup = function(e){
    e = e || event; // to deal with IE
    map[e.keyCode] = e.type == 'keydown';
    /* insert conditional here */
}

ce code est très simple: comme l'ordinateur ne passe qu'une touche à la fois, un tableau est créé pour garder la trace de plusieurs touches. Le tableau peut ensuite être utilisé pour vérifier une ou plusieurs touches à la fois.

juste pour expliquer, disons que vous appuyez sur A et B , chacun déclenche un keydown événement qui fixe map[e.keyCode] à la valeur de e.type == keydown , qui évalue à vrai ou faux . Maintenant les deux map[65] et map[66] sont définis à true . Lorsque vous lâchez A , l'événement keyup se déclenche, provoquant la même logique pour déterminer le résultat opposé pour map[65] (A), qui est maintenant faux , mais depuis map[66] (B) est toujours " en baisse "(il n'a pas déclenché un événement keyup), il reste vrai .

le tableau map , à travers les deux événements, ressemble à ceci:

// keydown A 
// keydown B
[
    65:true,
    66:true
]
// keyup A
// keydown B
[
    65:false,
    66:true
]

Il y a deux choses que vous pouvez faire maintenant:

a) un enregistreur de clés ( exemple ) peut être créé comme référence pour plus tard quand vous voulez rapidement comprendre un ou plusieurs codes clés. En supposant que vous ont défini un élément html et l'ont pointé vers la variable element .

element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
    if(map[i]){
        element.innerHTML += '<hr>' + i;
    }
}

Note: Vous pouvez facilement saisir un élément par son attribut id .

<div id="element"></div>

cela crée un élément html qui peut être facilement référencé en javascript avec element

alert(element); // [Object HTMLDivElement]

Vous n'avez même pas à utiliser document.getElementById() ou $() pour l'attraper. Mais pour des raisons de compatibilité, l'utilisation de jQuery $() est plus largement recommandé.

assurez-vous que la balise script vient après le corps du HTML. Conseil D'optimisation : la plupart des sites Web de grande renommée mettent l'étiquette de script après l'étiquette de corps pour l'optimisation. C'est parce que la balise script bloque d'autres éléments du chargement jusqu'à ce que son script soit terminé. Le mettre en avant du contenu permet au contenu de se charger préalablement.

B (qui est où votre intérêt réside) vous pouvez vérifier pour une ou plusieurs clés à la fois où /*insert conditional here*/ était, prendre cet exemple:

if(map[17] && map[16] && map[65]){ // CTRL+SHIFT+A
    alert('Control Shift A');
}else if(map[17] && map[16] && map[66]){ // CTRL+SHIFT+B
    alert('Control Shift B');
}else if(map[17] && map[16] && map[67]){ // CTRL+SHIFT+C
    alert('Control Shift C');
}

modifier : ce n'est pas l'extrait le plus lisible. La lisibilité est importante, donc vous pouvez essayer quelque chose comme ça pour le rendre plus facile sur les yeux:

function test_key(selkey){
    var alias = {
        "ctrl":  17,
        "shift": 16,
        "A":     65,
        /* ... */
    };

    return key[selkey] || key[alias[selkey]];
}

function test_keys(){
    var keylist = arguments;

    for(var i = 0; i < keylist.length; i++)
        if(!test_key(keylist[i]))
            return false;

    return true;
}

Usage:

test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')

c'est mieux?

if(test_keys('ctrl', 'shift')){
    if(test_key('A')){
        alert('Control Shift A');
    } else if(test_key('B')){
        alert('Control Shift B');
    } else if(test_key('C')){
        alert('Control Shift C');
    }
}

(fin de l'édition)


Cet exemple vérifie Ctrl Shift Un , Ctrl Shift B , et Ctrl Shift C

C'est aussi simple que cela:)

Notes

Garder la Trace des codes de touche

en règle générale, il est de bonne pratique de documenter le code, surtout des choses comme les codes clés (comme // CTRL+ENTER ) pour que vous puissiez vous rappeler ce qu'ils étaient.

vous devez également mettre les codes clés dans le même ordre que la documentation ( CTRL+ENTER => map[17] && map[13] , pas map[13] && map[17] ). De cette façon, vous ne jamais soyez confus lorsque vous devez revenir en arrière et modifier le code.

Une chasse aux sorcières avec des if-else chaînes

si le contrôle pour des combos de montants différents (comme Ctrl Shift Alt entrer et Ctrl entrer ), mettez des combos plus petits après combos plus grands, ou bien les combos plus petits annulera les plus gros combos s'ils sont assez semblables. Exemple:

// Correct:
if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!')
}

// Incorrect:
if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!');
}
// What will go wrong: When trying to do CTRL+SHIFT+ENTER, it will
// detect CTRL+ENTER first, and override CTRL+SHIFT+ENTER.
// Removing the else's is not a proper solution, either
// as it will cause it to alert BOTH "Mr. Power user" AND "You Found Me"

Gotcha: "Cette combinaison de touche garde activation, même si je ne suis pas en appuyant sur les touches"

lorsque vous avez affaire à des alertes ou à quelque chose qui prend la mise au point de la fenêtre principale, vous pourriez vouloir inclure map = [] pour réinitialiser le tableau une fois que la condition est terminée. C'est parce que certaines choses, comme alert() , enlever la mise au point de la fenêtre principale et provoquer la 'keyup' événement à ne pas déclencher. Par exemple:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Oh noes, a bug!');
}
// When you Press any key after executing this, it will alert again, even though you 
// are clearly NOT pressing CTRL+ENTER
// The fix would look like this:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Take that, bug!');
    map = {};
}
// The bug no longer happens since the array is cleared

Gotcha: Le Navigateur Par Défaut

Voici une chose ennuyeuse que j'ai trouvé, avec la solution incluse:

problème: depuis le navigateur a habituellement des actions par défaut sur les combos clés (comme Ctrl "1519930920 D active la fenêtre du signet, ou Ctrl Shift C active skynote sur maxthon), vous pourriez également vouloir ajouter return false après map = [] , de sorte que les utilisateurs de votre site ne seront pas frustrés lorsque la fonction" Duplicate File", étant mis sur Ctrl D , marque la page à la place.

if(map[17] && map[68]){ // CTRL+D
    alert('The bookmark window didn\'t pop up!');
    map = {};
    return false;
}

sans return false , la fenêtre de marque-page apparaîtrait à la consternation de l'utilisateur.

Le retour déclaration de la (nouvelle)

Ok, donc vous ne voulez pas toujours quitter la fonction à ce point. C'est pourquoi la fonction event.preventDefault() est présente. Ce qu'il fait est de mettre un drapeau interne qui dit à l'interpréteur et non permettre au navigateur d'exécuter son action par défaut. Après cela, l'exécution de la fonction continue (alors que return sortira immédiatement de la fonction).

comprenez cette distinction avant de décider utilisation de return false ou e.preventDefault()

event.keyCode est obsolète

Utilisateur SeanVieira , a souligné dans les commentaires que event.keyCode est obsolète.

là, il a donné une excellente alternative: event.key , qui renvoie une représentation en chaîne de la clé étant pressé, comme "a" pour A , ou "Shift" pour Shift .

je suis allé de l'avant et a préparé un outil pour examiner lesdites cordes.

element.onevent vs element.addEventListener

Les manipulateurs

enregistrés avec addEventListener peuvent être empilés, et sont appelés dans l'ordre d'enregistrement, tandis que le réglage .onevent directement est plutôt agressif et remplace tout ce que vous aviez auparavant.

document.body.onkeydown = function(ev){
    // do some stuff
    ev.preventDefault(); // cancels default actions
    return false; // cancels this function as well as default actions
}

document.body.addEventListener("keydown", function(ev){
    // do some stuff
    ev.preventDefault() // cancels default actions
    return false; // cancels this function only
});

le .onevent la propriété semble ignorer tout et le comportement de ev.preventDefault() et return false; peut être assez imprévisible.

dans les deux cas, les manipulateurs enregistrés via addEventlistener semblent être plus faciles à écrire et à raisonner.

il y a aussi attachEvent("onevent", callback) de L'implémentation non-standard D'Internet Explorer, mais ceci est au-delà de deprecated et ne concerne même pas JavaScript (il se rapporte à un langage ésotérique appelé JScript ). Il serait dans votre intérêt d'éviter autant que possible le code polyglotte.

Une classe helper

pour répondre à la confusion / plaintes, j'ai écrit une "classe" qui fait cette abstraction ( pastebin link ):

function Input(el){
    var parent = el,
        map = {},
        intervals = {};

    function ev_kdown(ev)
    {
        map[ev.key] = true;
        ev.preventDefault();
        return;
    }

    function ev_kup(ev)
    {
        map[ev.key] = false;
        ev.preventDefault();
        return;
    }

    function key_down(key)
    {
        return map[key];
    }

    function keys_down_array(array)
    {
        for(var i = 0; i < array.length; i++)
            if(!key_down(array[i]))
                return false;

        return true;
    }

    function keys_down_arguments()
    {
        return keys_down_array(Array.from(arguments));
    }

    function clear()
    {
        map = {};
    }

    function watch_loop(keylist, callback)
    {
        return function(){
            if(keys_down_array(keylist))
                callback();
        }
    }

    function watch(name, callback)
    {
        var keylist = Array.from(arguments).splice(2);

        intervals[name] = setInterval(watch_loop(keylist, callback), 1000/24);
    }

    function unwatch(name)
    {
        clearInterval(intervals[name]);
        delete intervals[name];
    }

    function detach()
    {
        parent.removeEventListener("keydown", ev_kdown);
        parent.removeEventListener("keyup", ev_kup);
    }

    function attach()
    {
        parent.addEventListener("keydown", ev_kdown);
        parent.addEventListener("keyup", ev_kup);
    }

    function Input()
    {
        attach();

        return {
            key_down: key_down,
            keys_down: keys_down_arguments,
            watch: watch,
            unwatch: unwatch,
            clear: clear,
            detach: detach
        };
    }

    return Input();
}

cette classe ne fait pas tout et ne traitera pas tous les cas d'utilisation concevables. Je ne suis pas une bibliothèque guy. Mais pour une utilisation interactive générale, ça devrait aller.

pour utiliser cette classe, créez une instance et pointez-la vers l'élément que vous voulez associer à l'entrée clavier avec:

var input_txt = Input(document.getElementById("txt"));

input_txt.watch("print_5", function(){
    txt.value += "FIVE ";
}, "Control", "5");

ce que cela va faire est d'attacher un nouvel écouteur d'entrée à l'élément avec #txt (supposons que ce soit une textarea), et de mettre un point de guet pour le combo clé Ctrl+5 . Lorsque Ctrl et 5 sont désactivés, la fonction de rappel que vous avez transmise (dans ce cas, une fonction qui ajoute "FIVE " à le textarea) sera appelée. Le rappel est associée avec le nom print_5 , donc de l'enlever, il suffit d'utiliser:

input_txt.unwatch("print_5");

pour détacher input_txt de l'élément txt :

input_txt.detach();

de cette façon, la collecte des ordures peut ramasser l'objet ( input_txt ), s'il est jeté, et vous n'aurez pas un vieil écouteur d'événement zombie gauche plus.

pour la rigueur, voici une référence rapide à L'API de la classe, présentée dans le style C / Java pour que vous sachiez ce qu'ils retournent et quels arguments ils attendent.

Boolean  key_down (String key);

Renvoie true si key est en bas, sinon false.

Boolean  keys_down (String key1, String key2, ...);

Renvoie true si toutes les touches key1 .. keyN , false sinon.

void     watch (String name, Function callback, String key1, String key2, ...);

crée un" watchpoint "tel que la pression de tous les keyN déclenchera le callback

void     unwatch (String name);

supprime ladite montre via son nom

void     clear (void);

efface le cache" keys down". Équivalent à map = {} au-dessus de

void     detach (void);

détache les écouteurs ev_kdown et ev_kup de l'élément parent, ce qui permet de se débarrasser en toute sécurité de l'instance

mise à jour 2017-12-02 en réponse pour une demande de publication de ceci à github, j'ai créé un gist .

mise à jour 2018-07-21 j'ai joué avec la programmation de style déclarative depuis un certain temps, et cette voie est maintenant mon préféré personnel: fiddle , pastebin

Généralement, cela fonctionne avec les cas que vous voudriez (Ctrl, alt, shift), mais si vous avez besoin de frapper, dire, a+w en même temps, il ne serait pas trop difficile de" combiner " les approches en une recherche multicritère.


j'espère que cette bien expliqué réponse mini-blog a été très utile :)

238
répondu Braden Best 2018-07-24 20:37:09

vous devez utiliser l'événement keydown pour garder la trace des touches enfoncées, et vous devez utiliser l'événement keyup pour garder la trace de quand les touches sont libérées.

voir cet exemple: http://jsfiddle.net/vor0nwe/mkHsU/

(mise à jour: je reproduit le code ici, au cas où jsfiddle.net bails:) Le HTML:

<ul id="log">
    <li>List of keys:</li>
</ul>

...et le Javascript (en utilisant jQuery):

var log = $('#log')[0],
    pressedKeys = [];

$(document.body).keydown(function (evt) {
    var li = pressedKeys[evt.keyCode];
    if (!li) {
        li = log.appendChild(document.createElement('li'));
        pressedKeys[evt.keyCode] = li;
    }
    $(li).text('Down: ' + evt.keyCode);
    $(li).removeClass('key-up');
});

$(document.body).keyup(function (evt) {
    var li = pressedKeys[evt.keyCode];
    if (!li) {
       li = log.appendChild(document.createElement('li'));
    }
    $(li).text('Up: ' + evt.keyCode);
    $(li).addClass('key-up');
});

Dans cet exemple, j'utilise un tableau pour garder une trace de la touche pressée. Dans une application réelle, vous pourriez vouloir delete chaque élément une fois que leur clé associée a été libérée.

notez que bien que j'ai utilisé jQuery pour me faciliter la tâche dans cet exemple, le concept fonctionne tout aussi bien quand je travaille en Javascript "brut".

28
répondu Martijn 2013-11-22 11:06:49

j'ai utilisé cette méthode (j'ai dû vérifier où est Shift + Ctrl pressé):

// create some object to save all pressed keys
var keys = {
    shift: false,
    ctrl: false
};

$(document.body).keydown(function(event) {
// save status of the button 'pressed' == 'true'
    if (event.keyCode == 16) {
        keys["shift"] = true;
    } else if (event.keyCode == 17) {
        keys["ctrl"] = true;
    }
    if (keys["shift"] && keys["ctrl"]) {
        $("#convert").trigger("click"); // or do anything else
    }
});

$(document.body).keyup(function(event) {
    // reset status of the button 'released' == 'false'
    if (event.keyCode == 16) {
        keys["shift"] = false;
    } else if (event.keyCode == 17) {
        keys["ctrl"] = false;
    }
});
6
répondu Array 2013-07-19 15:13:02
document.onkeydown = keydown; 

function keydown (evt) { 

    if (!evt) evt = event; 

    if (evt.ctrlKey && evt.altKey && evt.keyCode === 115) {

        alert("CTRL+ALT+F4"); 

    } else if (evt.shiftKey && evt.keyCode === 9) { 

        alert("Shift+TAB");

    } 

}
6
répondu Eduardo La Hoz Miranda 2018-03-26 14:34:16

Faire le keydown même appeler des fonctions multiples, chaque fonction de vérification d'une clé spécifique et répondre de manière appropriée.

document.keydown = function (key) {

    checkKey("x");
    checkKey("y");
};
2
répondu AnonymousGuest 2015-09-09 02:18:20

pour qui a besoin du code d'exemple complet. Droit+gauche ajouté

var keyPressed = {};
document.addEventListener('keydown', function(e) {

   keyPressed[e.key + e.location] = true;

    if(keyPressed.Shift1 == true && keyPressed.Control1 == true){
        // Left shift+CONTROL pressed!
        keyPressed = {}; // reset key map
    }
    if(keyPressed.Shift2 == true && keyPressed.Control2 == true){
        // Right shift+CONTROL pressed!
        keyPressed = {};
    }

}, false);

document.addEventListener('keyup', function(e) {
   keyPressed[e.key + e.location] = false;

   keyPressed = {};
}, false);
1
répondu Reza.irdev 2017-08-30 08:10:16

Si l'une des touches Alt / Ctrl / Maj vous pouvez utiliser cette méthode:

document.body.addEventListener('keydown', keysDown(actions) );

function actions() {
   // do stuff here
}

// simultaneous pressing Alt + R
function keysDown (cb) {
  return function (zEvent) {
    if (zEvent.altKey &&  zEvent.code === "KeyR" ) {
      return cb()
    }
  }
}
1
répondu Michael Lester 2018-01-03 16:33:26

j'essaierais d'ajouter un keypress Event handler sur keydown . Par exemple:

window.onkeydown = function() {
    // evaluate key and call respective handler
    window.onkeypress = function() {
       // evaluate key and call respective handler
    }
}

window.onkeyup = function() {
    window.onkeypress = void(0) ;
}

ceci est juste destiné à illustrer un modèle; je ne vais pas entrer dans les détails ici (surtout pas dans le niveau spécifique au navigateur 2+ Event enregistrement).

Post retour s'il vous plaît si cela aide ou pas.

0
répondu FK82 2011-03-05 11:35:09
case 65: //A
jp = 1;
setTimeout("jp = 0;", 100);

if(pj > 0) {
ABFunction();
pj = 0;
}
break;

case 66: //B
pj = 1;
setTimeout("pj = 0;", 100);

if(jp > 0) {
ABFunction();
jp = 0;
}
break;

Pas la meilleure façon, je sais.

0
répondu Anonymous 2012-01-21 14:28:51