Nota: keyCode è ora obsoleto.
Il rilevamento di più tasti è facile se si capisce il concetto
Il modo in cui lo faccio è così:
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 */
}
Questo codice è molto semplice: poiché il computer passa solo una sequenza di tasti alla volta, viene creato un array per tenere traccia di più chiavi. L'array può quindi essere utilizzato per verificare una o più chiavi contemporaneamente.
Giusto per spiegare, diciamo che premi Ae B, ciascuno genera un keydown
evento che si imposta map[e.keyCode]
sul valore di e.type == keydown
, che viene valutato su vero o falso . Ora entrambi map[65]
e map[66]
sono impostati su true
. Quando lo lasci andare A
, l' keyup
evento si attiva, facendo sì che la stessa logica determini il risultato opposto permap[65]
(A), che ora è falso , ma poiché map[66]
(B) è ancora "inattivo" (non ha attivato un evento keyup), rimane vero .
L' map
array, attraverso entrambi gli eventi, si presenta così:
// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]
Ci sono due cose che puoi fare ora:
A) Un key logger ( esempio ) può essere creato come riferimento per in seguito quando si desidera capire rapidamente uno o più codici chiave. Supponendo di aver definito un elemento html e di averlo puntato con la variabileelement
.
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 dal suo id
attributo.
<div id="element"></div>
Questo crea un elemento HTML che può essere facilmente referenziato in JavaScript con element
alert(element); // [Object HTMLDivElement]
Non devi nemmeno usarlo document.getElementById()
o $()
afferrarlo. Ma per motivi di compatibilità, l'uso di jQuery $()
è più ampiamente raccomandato.
Assicurati solo che il tag dello script segua il corpo dell'HTML. Suggerimento per l'ottimizzazione : La maggior parte dei siti web di grandi nomi mettere il tag script dopo il tag body per l'ottimizzazione. Questo perché il tag dello script blocca il caricamento di ulteriori elementi fino al termine del download dello script. Metterlo davanti al contenuto consente il caricamento anticipato del contenuto.
B (che è dove si trova il tuo interesse) Puoi controllare una o più chiavi in un momento in cui si /*insert conditional here*/
trovava, 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 : non è lo snippet più leggibile. La leggibilità è importante, quindi potresti provare qualcosa del genere per rendere più facile la vista:
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 della modifica)
Questo esempio assegni per CtrlShiftA, CtrlShiftBeCtrlShiftC
È così semplice :)
Appunti
Tenere traccia dei KeyCodes
Come regola generale, è buona norma documentare il codice, in particolare cose come i codici chiave (come // CTRL+ENTER
) in modo da poter ricordare quali fossero.
Dovresti anche inserire i codici chiave nello stesso ordine della documentazione ( CTRL+ENTER => map[17] && map[13]
, NON map[13] && map[17]
). In questo modo non ti confonderai mai quando dovrai tornare indietro e modificare il codice.
Un gotcha con catene if-else
Se cerchi combo di quantità diverse (come CtrlShiftAltEntere CtrlEnter), metti combo più piccole dopo combo più grandi, altrimenti le combo più piccole sostituiranno 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: "Questa combinazione di tasti continua ad attivarsi anche se non premo i tasti"
Quando si ha a che fare con avvisi o qualsiasi cosa che attiri l'attenzione dalla finestra principale, è possibile che si desideri includere la map = []
reimpostazione dell'array al termine della condizione. Questo perché alcune cose, ad esempio alert()
, allontanano lo stato attivo dalla finestra principale e impediscono l'attivazione dell'evento 'keyup'. 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 array is cleared
Gotcha: valori predefiniti del browser
Ecco una cosa fastidiosa che ho trovato, con la soluzione inclusa:
Problema: poiché il browser di solito ha azioni predefinite su combinazioni di tasti (come ad esempio CtrlDattiva la finestra dei segnalibri o CtrlShiftCattiva skynote su maxthon), potresti anche voler aggiungere return false
dopo map = []
, quindi gli utenti del tuo sito non saranno frustrati quando il "File duplicato" la funzione, essendo attivata CtrlD, aggiunge invece la pagina ai segnalibri.
if(map[17] && map[68]){ // CTRL+D
alert('The bookmark window didn\'t pop up!');
map = {};
return false;
}
Senza return false
, la finestra dei segnalibri si aprirà, con sgomento dell'utente.
La dichiarazione di ritorno (nuova)
Va bene, quindi non sempre vuoi uscire dalla funzione in quel punto. Ecco perché la event.preventDefault()
funzione è lì. Ciò che fa è impostare un flag interno che dice all'interprete di non consentire al browser di eseguire la sua azione predefinita. Successivamente, l'esecuzione della funzione continua (mentre return
uscirà immediatamente dalla funzione).
Comprendi questa distinzione prima di decidere se utilizzare return false
oe.preventDefault()
event.keyCode
è deprecato
L'utente SeanVieira ha sottolineato nei commenti event.keyCode
deprecato.
Lì, ha dato un'ottima alternativa event.key
:, che restituisce una rappresentazione in forma di stringa del tasto premuto, come "a"
per Ao "Shift"
perShift .
Sono andato avanti e ho messo a punto uno strumento per esaminare tali stringhe.
element.onevent
vs element.addEventListener
I gestori registrati con addEventListener
possono essere impilati e chiamati nell'ordine di registrazione, mentre l'impostazione .onevent
diretta è piuttosto aggressiva e annulla qualsiasi cosa in precedenza.
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 .onevent
proprietà sembra sovrascrivere tutto e il comportamento di ev.preventDefault()
ereturn false;
può essere piuttosto imprevedibile.
In entrambi i casi, i gestori registrati tramite addEventlistener
sembrano essere più facili da scrivere e ragionare.
C'è anche attachEvent("onevent", callback)
dall'implementazione non standard di Internet Explorer, ma è oltremodo obsoleta e non riguarda nemmeno JavaScript (si riferisce a un linguaggio esoterico chiamato JScript ). Sarebbe nel tuo interesse evitare il codice poliglotta il più possibile.
Una classe di aiuto
Per risolvere confusione / reclami, ho scritto una "classe" che fa questa astrazione ( link 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();
}
Questa classe non fa tutto e non gestisce tutti i possibili casi d'uso. Non sono un ragazzo delle biblioteche. Ma per un uso interattivo generale dovrebbe andare bene.
Per utilizzare questa classe, creare un'istanza e puntarla sull'elemento a cui si desidera associare l'input da tastiera:
var input_txt = Input(document.getElementById("txt"));
input_txt.watch("print_5", function(){
txt.value += "FIVE ";
}, "Control", "5");
Ciò che farà sarà collegare un nuovo listener di input all'elemento con #txt
(supponiamo che sia una textarea) e impostare un punto di controllo per la combinazione di tasti Ctrl+5
. Quando entrambi Ctrl
e 5
sono inattivi, verrà chiamata la funzione di callback che hai passato (in questo caso, una funzione che si aggiunge "FIVE "
alla textarea). Il callback è associato al nome print_5
, quindi per rimuoverlo basta usare:
input_txt.unwatch("print_5");
Per staccare input_txt
dal txt
elemento:
input_txt.detach();
In questo modo, la garbage collection può raccogliere l'oggetto ( input_txt
), nel caso in cui fosse gettato via, e non avresti più un vecchio ascoltatore di eventi zombi.
Per completezza, ecco un rapido riferimento all'API della classe, presentata in stile C / Java in modo da sapere cosa restituiscono e quali argomenti si aspettano.
Boolean key_down (String key);
Restituisce true
se key
è inattivo, falso altrimenti.
Boolean keys_down (String key1, String key2, ...);
Restituisce true
se tutte le chiavi key1 .. keyN
sono inattive, false in caso contrario.
void watch (String name, Function callback, String key1, String key2, ...);
Crea un "punto di controllo" in modo tale che premendo tutto keyN
si attiverà la richiamata
void unwatch (String name);
Rimuove detto punto di controllo tramite il suo nome
void clear (void);
Cancella la cache "chiavi in giù". Equivalente a map = {}
sopra
void detach (void);
Stacca gli ascoltatori ev_kdown
e ev_kup
dall'elemento genitore, rendendo possibile eliminare in modo sicuro l'istanza
Aggiornamento 02-12-2017 In risposta a una richiesta di pubblicazione su github, ho creato una sintesi .
Aggiornamento 21/07/2018 Ho giocato con la programmazione in stile dichiarativo per un po ', e in questo modo ora è il mio preferito: violino , pastebin
In generale, funzionerà con i casi che vorresti realisticamente (ctrl, alt, shift), ma se hai bisogno di colpire, diciamo, a+w
allo stesso tempo, non sarebbe troppo difficile "combinare" gli approcci in un multi-key-lookup.
Spero che questa risposta completamente spiegata mini-blog sia stata utile :)