Molti tasti JavaScript sono stati premuti contemporaneamente

Sto cercando di sviluppare un motore di gioco javascript e ho incontrato questo problema:

Quando premo lo spazio, il personaggio salta. Quando premo la freccia destra il personaggio si muove a destra.

La cosa è che quando continuo a premere a destra, premo lo spazio che il personaggio salta, quindi smette di muoversi.

Utilizzo la function keydown per premere il tasto, come posso controllare se ci sono più tasti premuti?

Il rilevamento di più tasti è facile se si comprende il concetto

Il mio modo di fare è come questo:

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

Questo codice è molto semplice: dal momento che il computer passa solo una battuta alla volta, viene creato un arrays per tenere traccia di più chiavi. L'arrays può quindi essere utilizzato per verificare una o più chiavi contemporaneamente.

Basta spiegare, diciamo che premete A e B , ognuno accende un evento keydown che imposta la map[e.keyCode] al valore di e.type == keydown , che valuta sia vero che falso . Ora la map[65] e la map[66] sono impostate su true . Quando si lascia A , l'evento di keyup si accende, causando la stessa logica di determinare il risultato opposto per la map[65] (A), che è ora falsa , ma poiché la map[66] (B) è ancora "scesa" ( non ha triggersto un evento keyup), rimane vera .

L'arrays della map , attraverso entrambi gli events, ha il seguente aspetto:

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

Ci sono due cose che puoi fare adesso:

A) Un registro chiave ( esempio ) può essere creato come riferimento per più tardi quando si desidera individuare rapidamente uno o più codici chiave. Supponendo di aver definito un elemento html e indicato con l' element variabile.

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

Nota: puoi facilmente afferrare un elemento per il suo attributo id .

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

Ciò crea un elemento html che può essere facilmente riferito a javascript con element

 alert(element); // [Object HTMLDivElement] 

Non è neppure necessario utilizzare document.getElementById() o $() per afferrarlo. Ma per motivi di compatibilità, l'uso di $() di jQuery è più ampiamente raccomandato.

Assicurati solo che il tag script venga dopo il corpo dell'HTML. Suggerimento per l'ottimizzazione : la maggior parte dei siti web di grandi size mette il tag di script dopo l'etichetta del corpo per ottimizzare. Ciò è dovuto al fatto che il tag di script blocca altri elementi dal caricamento finché il suo script non è terminato. Mettendolo avanti al contenuto consente di caricare il contenuto in precedenza.

B (where è il tuo interesse) Puoi controllare una o più chiavi in ​​un momento in cui /*insert conditional here*/ stato, prendi questo esempio:

 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'); } 

Modifica : questo non è lo snippet più leggibile. La leggerezza è importnte, quindi potresti provare qualcosa di simile per renderlo più facile agli occhi:

 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; } 

Uso:

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

È meglio questo?

 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'); } } 

(fine modifica)


Questo esempio controlla Ctrl Shift A , Ctrl Shift B e Ctrl Shift C

E 'altrettanto semplice:

Gli appunti

Tenere traccia dei codici chiave

Come regola generale, è buona norma documentare il codice, in particolare cose come i codici chiave (come // CTRL+ENTER ) in modo da poter ricordare quello che erano.

Dovresti anche mettere i codici chiave nello stesso ordine della documentazione ( CTRL+ENTER => map[17] && map[13] , NOT map[13] && map[17] ). In questo modo non sarai mai confuso quando dovrai tornare indietro e modificare il codice.

Una gotcha con le catene if-else

Se si verificano combinazioni di quantità diverse (come Ctrl Shift Alt Enter e Ctrl Enter ), si possono mettere combo più piccoli dopo le più grandi combo, altrimenti le combo più piccole sovrascrivono le combo più grandi se sono abbastanza simili. Esempio:

 // 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: "Questo combo chiave continua ad triggersre anche se non sto premendo le chiavi"

Quando si tratta di avvisi o di qualsiasi altra cosa che esegue la messa a fuoco dalla window principale, è ansible includere map = [] per reimpostare l'arrays dopo la condizione. Ciò è dovuto al fatto che alcune cose, come l' alert() , tengono l'attenzione dalla window principale e causano che l'evento "keyup" non venga triggersto. Per esempio:

 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 arrays is cleared 

Gotcha: Defaults del browser

Ecco una cosa fastidiosa che ho trovato, con la soluzione inclusa:

Problema: poiché il browser ha solitamente azioni predefinite sulle combo chiave (come Ctrl D triggers la window del segnalibro o Ctrl Shift C triggers lo skynote su maxthon), si potrebbe anche aggiungere il return false dopo map = [] , in modo che gli utenti del tuo sito non sarà frustrato quando la function "Duplicate File", inserita in Ctrl D , invece segnala la pagina.

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

Senza return false , la window dei segnalibri si aprirà, allo sgomento dell'utilizzatore.

La dichiarazione di return (nuova)

Okay, quindi non vuoi sempre uscire dalla function in quel momento. Ecco perché la function event.preventDefault() è presente. Quello che fa è impostare una bandiera interna che indichi all'interpnetworking di non consentire al browser di eseguire l'azione predefinita. Successivamente, l'esecuzione della function continua (mentre il return immediatamente esce dalla function).

Comprendere questa distinzione prima di decidere se utilizzare return false o e.preventDefault()

event.keyCode è deprecato

L'utente SeanVieira ha sottolineato nei commenti che l' event.keyCode è deprecato.

Là, ha dato un'ottima alternativa: event.key , che restituisce una rappresentazione di string della chiave premuta, come "a" per A , o "Shift" per Shift .

Sono andato avanti e preparato uno strumento per esaminare tali stringhe.

element.onevent vs element.addEventListener

I gestori registrati con addEventListener possono essere impilati e sono chiamati nell'ordine di logging, mentre l'impostazione di .onevent direttamente è piuttosto aggressivo e sostituisce qualsiasi cosa avesse precedentemente avuto.

 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 properties; .onevent sembra ignorare tutto e il comportmento di ev.preventDefault() e return false; può essere piuttosto imprevedibile.

In entrambi i casi, i gestori registrati tramite addEventlistener sembrano essere più facili da scrivere e da motivare.

C'è anche un attachEvent("onevent", callback) da implementazione non standard di Internet Explorer, ma questo non è più deprecato e non riguarda nemless il JavaScript (si tratta di un linguaggio esoterico chiamato JScript ). Sarebbe nel tuo interesse evitare il codice di poliglotta il più ansible.

Una class di aiuto

Per affrontare la confusione / le denunce, ho scritto una "class" che fa questa astrazione ( link di 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_arrays(arrays) { for(var i = 0; i < arrays.length; i++) if(!key_down(arrays[i])) return false; return true; } function keys_down_arguments() { return keys_down_arrays(Array.from(arguments)); } function clear() { map = {}; } function watch_loop(keylist, callback) { return function(){ if(keys_down_arrays(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(); } 

Questa class non fa tutto e non gestirà each caso d'uso concepibile. Non sono un tipo di biblioteca. Ma per un uso interattivo generale dovrebbe essere benissimo.

Per utilizzare questa class, creare un'istanza e indicarla all'elemento che si desidera associare l'input della tastiera con:

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

Ciò che farà è attaccare un nuovo listener di input all'elemento con #txt (supponiamo che sia una textarea) e impostare un orologio per il combo Ctrl+5 . Quando entrambi i Ctrl e 5 sono in discesa, verrà chiamata la function di richiamata (in questo caso, una function che aggiunge "FIVE " all'area di text). Il callback è associato al nome print_5 , per rimuoverlo, è sufficiente:

 input_txt.unwatch("print_5"); 

Per staccare input_txt dall'elemento txt :

 input_txt.detach(); 

In questo modo, la raccolta di rifiuti può raccogliere l'object ( input_txt ), se deve essere gettato via e non avrai più un vecchio ascoltatore di events zombie.

Per approfondire, qui è un rapido riferimento all'API della class, presentato in stile C / Java in modo da sapere cosa tornano e quali argomenti si aspettano.

 Boolean key_down (String key); 

Restituisce true se la key è giù, altrimenti false.

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

Restituisce true se tutti i tasti key1 .. keyN sono giù, altrimenti false.

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

Crea un "watchpoint" in modo tale che premendo tutto il keyN attiverà la richiamata

 void unwatch (String name); 

Rimuove detto watchpoint tramite il suo nome

 void clear (void); 

Pulisce la cache "chiavi in ​​giù". Equivalente alla map = {} sopra

 void detach (void); 

Stacca gli ev_kdown e ev_kup ascoltatori dall'elemento principale, consentendo di sbarazzarsi sicuramente dell'istanza


Spero che questa risposta spiegata è stata utile 🙂

È necessario utilizzare l'evento keydown per tenere traccia dei tasti premuti e utilizzare l'evento keyup per tenere traccia di quando vengono rilasciati i tasti.

Vedere questo esempio: http://jsfiddle.net/vor0nwe/mkHsU/

(Aggiorna: sto riproducendo il codice qui, nel caso jsfiddle.net bail 🙂 L'HTML:

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

… e il Javascript (utilizzando 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'); }); 

In questo esempio, sto utilizzando un arrays per tenere traccia di quali chiavi vengono premuti. In un'applicazione reale, è ansible delete each elemento una volta rilasciata la chiave associata.

Si noti che mentre ho usato jQuery per rendere le cose più semplici per me stesso in questo esempio, il concetto funziona altrettanto bene quando si lavora in Javascript 'crudo'.

Ho usato questo modo (wherevo controllare ovunque sia Shift + Ctrl premuto):

 // 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"); } } 

Rendere la keydown anche call più funzioni, con each function che controlla una chiave specifica e risponde in modo appropriato.

 document.keydown = function (key) { checkKey("x"); checkKey("y"); }; 

Vorrei provare ad aggiungere un gestore di Event a keydown su keydown . Per esempio:

 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) ; } 

Questo è solo per illustrare un model; Non andrò in dettaglio qui (specialmente non in browser specific level2 + Registrazione Event ).

Torna indietro per favore se questo aiuta o no.

 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; 

Non è il modo migliore, lo so.

per chi ha bisogno di codice completo di esempio. A destra + sinistra aggiunto

 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);