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?
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
sikey
est en bas, sinon false.Boolean keys_down (String key1, String key2, ...);
Renvoie
true
si toutes les toucheskey1 .. 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 callbackvoid unwatch (String name);
supprime ladite montre via son nom
void clear (void);
efface le cache" keys down". Équivalent à
map = {}
au-dessus devoid detach (void);
détache les écouteurs
ev_kdown
etev_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 :)
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".
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;
}
});
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");
}
}
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");
};
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);
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()
}
}
}
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.
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.