Nota: keyCode ahora está en desuso.
La detección de pulsaciones múltiples es fácil si comprende el concepto
La forma en que lo hago es así:
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 */
}
Este código es muy simple: dado que la computadora solo pasa una pulsación de tecla a la vez, se crea una matriz para realizar un seguimiento de varias teclas. La matriz se puede usar para verificar una o más claves a la vez.
Solo para explicar, digamos que presiona Ay B, cada uno dispara un keydown
evento que se establece map[e.keyCode]
en el valor de e.type == keydown
, que se evalúa como verdadero o falso . Ahora ambos map[65]
y map[66]
están configurados en true
. Cuando lo suelta A
, el keyup
evento se dispara, causando que la misma lógica determine el resultado opuesto para map[65]
(A), que ahora es falso , pero dado que map[66]
(B) todavía está "inactivo" (no ha activado un evento de keyup), Sigue siendo cierto .
La map
matriz, a través de ambos eventos, se ve así:
// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]
Hay dos cosas que puedes hacer ahora:
A) Se puede crear un registrador de claves ( ejemplo ) como referencia para más adelante cuando desee descubrir rápidamente uno o más códigos clave. Suponiendo que haya definido un elemento html y lo haya señalado con la variable element
.
element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
if(map[i]){
element.innerHTML += '<hr>' + i;
}
}
Nota: Puede agarrar fácilmente un elemento por su id
atributo.
<div id="element"></div>
Esto crea un elemento html al que se puede hacer referencia fácilmente en javascript con element
alert(element); // [Object HTMLDivElement]
Ni siquiera tiene que usarlo document.getElementById()
o $()
agarrarlo. Pero en aras de la compatibilidad, el uso de jQuery $()
es más ampliamente recomendado.
Solo asegúrese de que la etiqueta del script venga después del cuerpo del HTML. Consejo de optimización : la mayoría de los sitios web de renombre colocan la etiqueta del script después de la etiqueta del cuerpo para la optimización. Esto se debe a que la etiqueta del script bloquea la carga de elementos adicionales hasta que el script termina de descargarse. Ponerlo por delante del contenido permite que el contenido se cargue de antemano.
B (que es donde radica su interés) Puede verificar una o más claves a la vez donde /*insert conditional here*/
estaba, tome este ejemplo:
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');
}
Editar : ese no es el fragmento más legible. La legibilidad es importante, por lo que podría intentar algo como esto para que sea más fácil para 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')
¿Es esto mejor?
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 la edición)
Este ejemplo cheques por CtrlShiftA, CtrlShiftByCtrlShiftC
Es tan simple como eso :)
Notas
Seguimiento de KeyCodes
Como regla general, es una buena práctica documentar el código, especialmente cosas como los códigos clave (como // CTRL+ENTER
) para que pueda recordar cuáles eran.
También debe colocar los códigos clave en el mismo orden que la documentación ( CTRL+ENTER => map[17] && map[13]
, NO map[13] && map[17]
). De esta manera, nunca se confundirá cuando necesite volver y editar el código.
Un gotcha con cadenas if-else
Si busca combinaciones de diferentes cantidades (como CtrlShiftAltEntery CtrlEnter), coloque combinaciones más pequeñas después de combinaciones más grandes, de lo contrario, las combinaciones más pequeñas anularán las combinaciones más grandes si son lo suficientemente similares. Ejemplo:
// 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: "Este combo de teclas se sigue activando aunque no presione las teclas"
Cuando se trata de alertas o cualquier cosa que se enfoca desde la ventana principal, es posible que desee incluir map = []
para restablecer la matriz después de que se cumpla la condición. Esto se debe a que algunas cosas, comoalert()
, alejan el foco de la ventana principal y hacen que el evento 'keyup' no se active. Por ejemplo:
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: valores predeterminados del navegador
Aquí hay una cosa molesta que encontré, con la solución incluida:
Problema: dado que el navegador generalmente tiene acciones predeterminadas en combinaciones de teclas (como CtrlDactiva la ventana de marcadores o CtrlShiftCactiva skynote en maxthon), es posible que también desee agregar return false
después map = []
, para que los usuarios de su sitio no se sientan frustrados cuando el "Duplicar archivo" la función, que se pone CtrlD, marca la página como marcador
if(map[17] && map[68]){ // CTRL+D
alert('The bookmark window didn\'t pop up!');
map = {};
return false;
}
Sin return false
, la ventana Marca haría pop-up, para el disgusto del usuario.
La declaración de devolución (nuevo)
Bien, entonces no siempre quieres salir de la función en ese punto. Es por eso que la event.preventDefault()
función está ahí. Lo que hace es establecer un indicador interno que le dice al intérprete que no permita que el navegador ejecute su acción predeterminada. Después de eso, la ejecución de la función continúa (mientras return
que inmediatamente saldrá de la función).
Comprenda esta distinción antes de decidir si usar return false
oe.preventDefault()
event.keyCode
es obsoleto
El usuario SeanVieira señaló en los comentarios que event.keyCode
está en desuso.
Allí, dio una excelente alternativa: event.key
que devuelve una representación en cadena de la tecla que se está presionando, como "a"
for Ao "Shift"
for Shift.
Seguí adelante y preparé una herramienta para examinar dichas cadenas.
element.onevent
vs element.addEventListener
Los controladores registrados con addEventListener
se pueden apilar y se llaman en el orden de registro, mientras que la configuración .onevent
directa es bastante agresiva y anula todo lo que tenía anteriormente.
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
propiedad parece anular todo y el comportamiento de ev.preventDefault()
y return false;
puede ser bastante impredecible.
En cualquier caso, los controladores registrados a través de addEventlistener
parecen ser más fáciles de escribir y razonar.
También existe attachEvent("onevent", callback)
una implementación no estándar de Internet Explorer, pero esto está más allá de obsoleto y ni siquiera pertenece a JavaScript (pertenece a un lenguaje esotérico llamado JScript ). Le conviene evitar el código políglota tanto como sea posible.
Una clase auxiliar
Para abordar la confusión / quejas, he escrito una "clase" que hace esta abstracción ( enlace de 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();
}
Esta clase no hace todo y no manejará todos los casos de uso imaginables. No soy un chico de la biblioteca. Pero para uso interactivo general, debería estar bien.
Para usar esta clase, cree una instancia y apúntela al elemento con el que desea asociar la entrada del teclado:
var input_txt = Input(document.getElementById("txt"));
input_txt.watch("print_5", function(){
txt.value += "FIVE ";
}, "Control", "5");
Lo que esto hará es adjuntar un nuevo oyente de entrada al elemento con #txt
(supongamos que es un área de texto) y establecer un punto de observación para el combo de teclas Ctrl+5
. Cuando ambos Ctrl
y 5
están abajo, la función de devolución de llamada que ha pasado en (en este caso, una función que añade "FIVE "
al área de texto) se llamará. La devolución de llamada está asociada con el nombre print_5
, por lo que para eliminarlo, simplemente use:
input_txt.unwatch("print_5");
Para separarse input_txt
del txt
elemento:
input_txt.detach();
De esta manera, la recolección de basura puede recoger el objeto ( input_txt
), en caso de que se deseche, y no quedará un viejo oyente de eventos zombie.
Para mayor detalle, aquí hay una referencia rápida a la API de la clase, presentada en estilo C / Java para que sepa qué devuelven y qué argumentos esperan.
Boolean key_down (String key);
Devuelve true
si key
está abajo, falso de lo contrario.
Boolean keys_down (String key1, String key2, ...);
Devuelve true
si todas las teclas key1 .. keyN
están abajo, falso de lo contrario.
void watch (String name, Function callback, String key1, String key2, ...);
Crea un "punto de observación" tal que presionar todo keyN
activará la devolución de llamada
void unwatch (String name);
Elimina dicho punto de observación a través de su nombre.
void clear (void);
Limpia el caché de "teclas abajo". Equivalente a lo map = {}
anterior
void detach (void);
Separa el ev_kdown
y ev_kup
oyentes del elemento padre, por lo que es posible deshacerse de manera segura de la instancia
Actualización 2017-12-02 En respuesta a una solicitud para publicar esto en github, he creado una esencia .
Actualización 2018-07-21 He estado jugando con programación de estilo declarativo durante un tiempo, y de esta manera ahora es mi favorito personal: violín , pastebin
En general, funcionará con los casos que realmente desea (ctrl, alt, shift), pero si necesita golpear, digamos, a+w
al mismo tiempo, no sería demasiado difícil "combinar" los enfoques en un Búsqueda de teclas múltiples.
Espero que este mini-blog explicado a fondo haya sido útil :)