J'essaie de développer un moteur de jeu JavaScript et j'ai rencontré ce problème:
Le problème est que, lorsque j'appuie à droite puis sur Espace, le personnage saute puis cesse de bouger.
J'utilise la fonction keydown
pour obtenir la touche enfoncée. Comment puis-je vérifier si plusieurs touches sont enfoncées simultanément?
Remarque: le code de clé est maintenant obsolète.
La détection de frappe multiple est facile si vous comprenez le concept
Voici comment je le fais:
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: étant donné que l’ordinateur ne transmet qu’une frappe à la fois, un tableau est créé pour garder trace de plusieurs clés. Le tableau peut ensuite être utilisé pour rechercher une ou plusieurs clés à la fois.
Juste pour expliquer, disons que vous appuyez sur A et B, chacun déclenche un événement keydown
qui définit map[e.keyCode]
sur la valeur de e.type == keydown
, dont le résultat est true ou faux . Maintenant, map[65]
et map[66]
sont tous deux réglés sur true
. Lorsque vous relâchez A
, l'événement keyup
se déclenche, obligeant la même logique à déterminer le résultat opposé pour map[65]
(A), qui est maintenant false , mais puisque map[66]
(B) est toujours "en panne" (il n'a pas déclenché d'é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é ( exemple ) peut être créé en tant que référence pour plus tard, lorsque vous souhaitez déterminer rapidement un ou plus de codes clés. En supposant que vous ayez défini un élément html et que vous y pointez avec la variable element
.
element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
if(map[i]){
element.innerHTML += '<hr>' + i;
}
}
Remarque: 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 besoin d'utiliser document.getElementById()
ou $()
pour le récupérer. Mais pour des raisons de compatibilité, l’utilisation de $()
de jQuery est plus largement recommandée.
Assurez-vous simplement que la balise du script vient après le corps du code HTML. Astuce d’optimisation : La plupart des sites Web portant un nom important placent la balise de script après le balise body pour l'optimisation. En effet, la balise de script empêche le chargement d'éléments supplémentaires jusqu'à la fin du téléchargement du script. Le faire passer avant le contenu permet au contenu de se charger à l'avance.
B (c'est ce qui vous intéresse) Vous pouvez rechercher une ou plusieurs clés à la fois où /*insert conditional here*/
était, prenons l'exemple suivant:
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');
}
Edit : Ce n'est pas l'extrait le plus lisible. La lisibilité est importante, vous pouvez donc essayer quelque chose comme ceci pour rendre les yeux plus faciles:
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')
Est-ce 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 montage)
Cet exemple vérifie CtrlShiftA, CtrlShiftB, et CtrlShiftC
C'est aussi simple que ça :)
En règle générale, il est recommandé de documenter le code, en particulier des éléments tels que les codes de clé (tels que // CTRL+ENTER
) afin que vous puissiez vous en souvenir.
Vous devez également placer les codes de clé dans le même ordre que la documentation (CTRL+ENTER => map[17] && map[13]
, NOT map[13] && map[17]
). De cette façon, vous ne serez jamais dérouté lorsque vous devrez revenir en arrière et modifier le code.
Si vous recherchez des combinaisons de quantités différentes (comme CtrlShiftAltEnter et CtrlEnter), mettez des combos plus petits après des combos plus grands, sinon les combos plus petits remplaceront les combos plus grands s'ils sont suffisamment similaires. 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"
Lorsque vous traitez avec des alertes ou avec tout ce qui prend le focus depuis la fenêtre principale, vous pouvez inclure map = []
pour réinitialiser le tableau une fois la condition remplie. En effet, certaines choses, comme alert()
, détournent le focus de la fenêtre principale et empêchent le déclenchement de l'événement 'keyup'. 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
Voici une chose ennuyeuse que j'ai trouvée, avec la solution incluse:
Problème: le navigateur ayant généralement des actions par défaut sur les combinaisons principales (comme CtrlD active la fenêtre de signet, ou CtrlShiftC active skynote sur maxthon), vous pouvez également ajouter return false
après map = []
, afin que les utilisateurs de votre site ne soient pas frustrés lorsque la fonction "Fichier en double" est activée. CtrlD, signets 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 Signet apparaîtrait , à la consternation de l'utilisateur.
Bon, vous ne voulez donc pas toujours quitter la fonction à ce stade. C'est pourquoi la fonction event.preventDefault()
est là. Cela active un indicateur interne qui indique à l'interprète de ne pas autoriser le navigateur à exécuter l'action par défaut. Après cela, l'exécution de la fonction continue (alors que return
quittera immédiatement la fonction).
Comprenez cette distinction avant de décider d’utiliser return false
ou e.preventDefault()
event.keyCode
est obsolèteL'utilisateur SeanVieira a signalé dans les commentaires que event.keyCode
est obsolète.
Là, il a donné une excellente alternative: event.key
, qui retourne une représentation sous forme de chaîne de la touche enfoncée, comme "a"
pour A, ou "Shift"
pour Shift.
Je suis allé de l'avant et ai cuit un outil pour examiner lesdites chaînes.
element.onevent
vs element.addEventListener
Les gestionnaires enregistrés avec addEventListener
peuvent être empilés et sont appelés dans l'ordre d'enregistrement, tandis que le réglage de .onevent
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
});
La propriété .onevent
semble tout écraser et le comportement de ev.preventDefault()
et return false;
peut être assez imprévisible.
Dans les deux cas, les gestionnaires enregistrés via addEventlistener
semblent plus faciles à écrire et à raisonner.
Il y a aussi attachEvent("onevent", callback)
de l'implémentation non standard d'Internet Explorer, mais elle est au-delà de obsolète et ne concerne même pas JavaScript (elle concerne un langage ésotérique appelé JScript ). Il serait dans votre intérêt d'éviter autant que possible le code polyglotte.
Pour faire face aux confusions/plaintes, j'ai écrit une "classe" qui fait cette abstraction ( lien Pastebin ):
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 elle ne gérera pas tous les cas d'utilisation imaginables. Je ne suis pas un gars de la bibliothèque. Mais pour une utilisation interactive générale, cela devrait aller.
Pour utiliser cette classe, créez une instance et pointez-la sur l'élément auquel vous souhaitez associer une entrée au clavier:
var input_txt = Input(document.getElementById("txt"));
input_txt.watch("print_5", function(){
txt.value += "FIVE ";
}, "Control", "5");
Cela va attacher un nouvel écouteur d’entrée à l’élément avec #txt
(supposons que c’est une zone de texte), et définir un point de contrôle pour le combo clé Ctrl+5
. Lorsque Ctrl
et 5
sont tous deux désactivés, la fonction de rappel que vous avez transmise (dans ce cas, une fonction qui ajoute "FIVE "
à la zone de texte) sera appelée. Le rappel est associé au nom print_5
, alors pour le supprimer, vous utilisez simplement:
input_txt.unwatch("print_5");
Pour détacher input_txt
de l'élément txt
:
input_txt.detach();
De cette façon, un ramasse-miettes peut récupérer l'objet (input_txt
), s'il devait être jeté, et vous n'aurez plus aucun écouteur d'événements zombie.
Pour des raisons de minutie, voici une référence rapide à l'API de la classe, présentée en style C/Java pour que vous sachiez ce qu'elles retournent et quels arguments elles attendent.
Boolean key_down (String key);
Retourne
true
sikey
est en panne, false sinon.Boolean keys_down (String key1, String key2, ...);
Retourne
true
si toutes les cléskey1 .. keyN
sont en panne, false sinon.void watch (String name, Function callback, String key1, String key2, ...);
Crée un "point de contrôle" tel que le fait d'appuyer sur
keyN
déclenchera le rappelvoid unwatch (String name);
Supprime ledit point de contrôle via son nom
void clear (void);
Efface le cache "clés". Équivalent à
map = {}
ci-dessusvoid detach (void);
Détache les écouteurs
ev_kdown
etev_kup
de l'élément parent, ce qui permet de supprimer l'instance en toute sécurité
Mise à jour 2017-12-02 En réponse à une demande de publication sur github, j'ai créé un Gist .
Mise à jour 2018-07-21 Cela fait un moment que je joue à la programmation déclarative, et cette façon est maintenant ma préférée: violon , Pastebin
Généralement, cela fonctionnera avec les cas que vous souhaiteriez de façon réaliste (ctrl, alt, shift), mais si vous devez appuyer sur, disons, a+w
en même temps, il ne sera pas trop difficile de "combiner" "les approches dans une recherche multi-clé.
J'espère Cela réponse bien expliquée mini-blog était utile :)
Vous devez utiliser l'événement keydown pour garder trace des touches enfoncées. et , vous devez utiliser les éléments événement keyup à suivre lorsque les touches sont relâchées.
Voir cet exemple: http://jsfiddle.net/vor0nwe/mkHsU/
(Mise à jour: je reproduis le code ici, au cas où jsfiddle.net échouerait :) Le code HTML:
<ul id="log">
<li>List of keys:</li>
</ul>
... et le Javascript (avec 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 savoir quelles touches sont enfoncées. Dans une application réelle, vous souhaiterez peut-être delete
chaque élément une fois la clé associée libérée.
Notez que bien que jQuery ait été simplifié pour moi dans cet exemple, le concept fonctionne tout aussi bien lorsque vous utilisez du code Javascript "brut".
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");
}
}
J'ai utilisé cette méthode (il fallait vérifier où l'on appuyait sur Maj + Ctrl):
// 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;
}
});
pour qui a besoin du code d'exemple complet. Right + Left 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);
Faites en sorte que le raccourci clavier appelle même plusieurs fonctions, chacune vérifiant une clé spécifique et y répondant correctement.
document.keydown = function (key) {
checkKey("x");
checkKey("y");
};
Si l'une des touches appuyée est Alt/Ctrl/Shift, 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()
}
}
}
Je voudrais essayer d'ajouter un gestionnaire keypress
Event
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 n'entrerai pas dans les détails ici (surtout pas dans l'enregistrement du niveau2 + Event
spécifique au navigateur).
Poster de retour s'il vous plaît si cela aide ou non.
$(document).ready(function () {
// using ascii 17 for ctrl, 18 for alt and 83 for "S"
// ctr+alt+S
var map = { 17: false, 18: false, 83: false };
$(document).keyup(function (e) {
if (e.keyCode in map) {
map[e.keyCode] = true;
if (map[17] && map[18] && map[83]) {
// Write your own code here, what you want to do
map[17] = false;
map[18] = false;
map[83] = false;
}
}
else {
// if u press any other key apart from that "map" will reset.
map[17] = false;
map[18] = false;
map[83] = false;
}
});
});
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 le meilleur moyen, je sais.