Antes de llegar al teclado, por favor ten en cuenta que en los dispositivos modernos hay otras formas de “ingresar algo”. Por ejemplo, el uso de reconocimiento de voz (especialmente en dispositivos móviles) o copiar/pegar con el mouse.
Entonces, si queremos hacer el seguimiento de cualquier ingreso en un campo <input>
, los eventos de teclado no son suficientes. Existe otro evento llamado input
para detectar cambios en un campo <input>
producidos por cualquier medio. Y puede ser una mejor opción para esa tarea. Lo estudiaremos más adelante, en el capítulo Eventos: change, input, cut, copy, paste.
Los eventos de teclado solo deberían ser usados cuando queremos manejar acciones de teclado (también cuentan los teclados virtuales). Por ejemplo, para reaccionar a las teclas de flecha Up y Down o a atajos de teclado “hotkeys” (incluyendo combinaciones de teclas).
Teststand
Para entender mejor los eventos de teclado, puedes usar “teststand” aquí abajo.
Prueba diferentes combinaciones de tecla en el campo de texto.
kinput.onkeydown = kinput.onkeyup = kinput.onkeypress = handle;
let lastTime = Date.now();
function handle(e) {
if (form.elements[e.type + 'Ignore'].checked) return;
area.scrollTop = 1e6;
let text = e.type +
' key=' + e.key +
' code=' + e.code +
(e.shiftKey ? ' shiftKey' : '') +
(e.ctrlKey ? ' ctrlKey' : '') +
(e.altKey ? ' altKey' : '') +
(e.metaKey ? ' metaKey' : '') +
(e.repeat ? ' (repeat)' : '') +
"\n";
if (area.value && Date.now() - lastTime > 250) {
area.value += new Array(81).join('-') + '\n';
}
lastTime = Date.now();
area.value += text;
if (form.elements[e.type + 'Stop'].checked) {
e.preventDefault();
}
}
#kinput {
font-size: 150%;
box-sizing: border-box;
width: 95%;
}
#area {
width: 95%;
box-sizing: border-box;
height: 250px;
border: 1px solid black;
display: block;
}
form label {
display: inline;
white-space: nowrap;
}
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<form id="form" onsubmit="return false">
Evitar el predeterminado (prevent default) para:
<label>
<input type="checkbox" name="keydownStop" value="1"> keydown</label>
<label>
<input type="checkbox" name="keyupStop" value="1"> keyup</label>
<p>
Ignore:
<label>
<input type="checkbox" name="keydownIgnore" value="1"> keydown</label>
<label>
<input type="checkbox" name="keyupIgnore" value="1"> keyup</label>
</p>
<p>Haz foco en el campo input y presiona una tecla.</p>
<input type="text" placeholder="Press keys here" id="kinput">
<textarea id="area" readonly></textarea>
<input type="button" value="Clear" onclick="area.value = ''" />
</form>
<script src="script.js"></script>
</body>
</html>
Keydown y keyup
Los eventos keydown
ocurren cuando se presiona una tecla, y keyup
cuando se suelta.
event.code y event.key
La propiedad key
del objeto de evento permite obtener el carácter, mientras que la propiedad code
del evento permite obtener el “código físico de la tecla”.
Por ejemplo, la misma tecla Z puede ser presionada con o sin Shift. Esto nos da dos caracteres diferentes: z
minúscula y Z
mayúscula.
event.key
es el carácter exacto, y será diferente. Pero event.code
es el mismo:
Tecla | event.key |
event.code |
---|---|---|
Z | z (minúscula) |
KeyZ |
Shift+Z | Z (mayúscula) |
KeyZ |
Si un usuario trabaja con diferentes lenguajes, el cambio a otro lenguaje podría producir un carácter totalmente diferente a "Z"
. Este se volverá el valor de event.key
, mientras que event.code
es siempre el mismo: "KeyZ"
.
Cada tecla tiene el código que depende de su ubicación en el teclado. Los códigos de tecla están descritos en la especificación UI Events code.
Por ejemplo:
- Las letras tienen códigos como
"Key<letter>"
:"KeyA"
,"KeyB"
etc. - Los dígitos tienen códigos como
"Digit<number>"
:"Digit0"
,"Digit1"
etc. - Las teclas especiales están codificadas por sus nombres:
"Enter"
,"Backspace"
,"Tab"
etc.
Hay varias distribuciones de teclado esparcidos, y la especificación nos da los códigos de tecla para cada una de ellas.
Para más códigos, puedes leer la sección alfanumérica de la especificación, o simplemente presionar una tecla en el teststand arriba.
"KeyZ"
, no "keyZ"
Parece obvio, pero aún se cometen estos errores.
Por favor evita errores de tipeo: es KeyZ
, no keyZ
. Una verificación como event.code=="keyZ"
no funcionará: la primera letra de "Key"
debe estar en mayúscula.
¿Qué pasa si una tecla no da ningún carácter? Por ejemplo, Shift o F1 u otras. Para estas teclas, event.key
es aproximadamente lo mismo que event.code
:
Key | event.key |
event.code |
---|---|---|
F1 | F1 |
F1 |
Backspace | Backspace |
Backspace |
Shift | Shift |
ShiftRight or ShiftLeft |
Ten en cuenta que event.code
especifica con exactitud la tecla que es presionada. Por ejemplo, la mayoría de los teclados tienen dos teclas Shift: una a la izquierda y otra a la derecha. event.code
nos dice exactamente cuál fue presionada, en cambio event.key
es responsable del “significado” de la tecla: lo que “es” (una “Mayúscula”).
Digamos que queremos manejar un atajo de teclado: Ctrl+Z (o Cmd+Z en Mac). La mayoría de los editores de texto “cuelgan” la acción “Undo” en él. Podemos configurar un “listener” para escuchar el evento keydown
y verificar qué tecla es presionada.
Hay un dilema aquí: en ese “listener”, ¿debemos verificar el valor de event.key
o el de event.code
?
Por un lado, el valor de event.key
es un carácter que cambia dependiendo del lenguaje. Si el visitante tiene varios lenguajes en el sistema operativo y los cambia, la misma tecla dará diferentes caracteres. Entonces tiene sentido chequear event.code
que es siempre el mismo.
Como aquí:
document.addEventListener('keydown', function(event) {
if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) {
alert('Undo!')
}
});
Por otro lado, hay un problema con event.code
. Para diferentes distribuciones de teclado, la misma tecla puede tener diferentes caracteres.
Por ejemplo, aquí abajo mostramos la distribución de EE.UU. “QWERTY” y la alemana “QWERTZ” (de Wikipedia):
Para la misma tecla, la distribución norteamericana tiene “Z”, mientras que la alemana tiene “Y” (las letras son intercambiadas).
Efectivamente, event.code
será igual a KeyZ
para las personas con distribución de teclas alemana cuando presionen Y.
Si chequeamos event.code == 'KeyZ'
en nuestro código, las personas con distribución alemana pasarán el test cuando presionen Y.
Esto suena realmente extraño, y lo es. La especificación explícitamente menciona este comportamiento.
Entonces, event.code
puede coincidir con un carácter equivocado en una distribución inesperada. Las mismas letras en diferentes distribuciones pueden mapear a diferentes teclas físicas, llevando a diferentes códigos. Afortunadamente, ello solo ocurre en algunos códigos, por ejemplo keyA
, keyQ
, keyZ
(que ya hemos visto), y no ocurre con teclas especiales como Shift
. Puedes encontrar la lista en la especificación.
Para un seguimiento confiable de caracteres que dependen de la distribución, event.key
puede ser una mejor opción.
Por otro lado, event.code
tiene el beneficio de quedar siempre igual, ligado a la ubicación física de la tecla. Así los atajos de teclado que dependen de él funcionan bien aunque cambie el lenguaje.
¿Queremos manejar teclas que dependen de la distribución? Entonces event.key
es lo adecuado.
¿O queremos que un atajo funcione en el mismo lugar incluso si cambia el lenguaje? Entonces event.code
puede ser mejor.
Autorepetición
Si una tecla es presionada durante suficiente tiempo, comienza a “autorepetirse”: keydown
se dispara una y otra vez, y cuando es soltada finalmente se obtiene keyup
. Por ello es normal tener muchos keydown
y un solo keyup
.
Para eventos disparados por autorepetición, el objeto de evento tiene la propiedad event.repeat
establecida a true
.
Acciones predeterminadas
Las acciones predeterminadas varían, al haber muchas cosas posibles que pueden ser iniciadas por el teclado.
Por ejemplo:
- Un carácter aparece en la pantalla (el resultado más obvio).
- Un carácter es borrado (tecla Delete).
- Un avance de página (tecla PageDown).
- El navegador abre el diálogo “guardar página” (Ctrl+S)
- …y otras.
Evitar la acción predeterminada en keydown
puede cancelar la mayoría de ellos, con la excepción de las teclas especiales basadas en el sistema operativo. Por ejemplo, en Windows la tecla Alt+F4 cierra la ventana actual del navegador. Y no hay forma de detenerla por medio de “evitar la acción predeterminada” de JavaScript.
Por ejemplo, el <input>
debajo espera un número telefónico, entonces no acepta teclas excepto dígitos, +
, ()
or -
:
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') || ['+','(',')','-'].includes(key);
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Teléfono, por favor" type="tel">
Aquí el manejador onkeydown
usa checkPhoneKey
para chequear la tecla presionada. Si es válida (de 0..9
o uno de +-()
), entonces devuelve true
, de otro modo, false
.
Como ya sabemos, el valor false
devuelto por el manejador de eventos, asignado usando una propiedad DOM o un atributo tal como lo hicimos arriba, evita la acción predeterminada; entonces nada aparece en <input>
para las teclas que no pasan el test. (El valor true
no afecta en nada, solo importa el valor false
)
Ten en cuenta que las teclas especiales como Backspace, Left, Right, no funcionan en el input. Este es un efecto secundario del filtro estricto que hace checkPhoneKey
. Estas teclas hacen que devuelva false
.
Aliviemos un poco el filtro permitiendo las tecla de flecha Left, Right, y Delete, Backspace:
<script>
function checkPhoneKey(key) {
return (key >= '0' && key <= '9') ||
['+','(',')','-','ArrowLeft','ArrowRight','Delete','Backspace'].includes(key);
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Teléfono, por favor" type="tel">
Ahora las flechas y el borrado funcionan bien.
Aunque tenemos el filtro de teclas, aún se puede ingresar cualquier cosa usando un mouse y “botón secundario + pegar”. Dispositivos móviles brindan otros medios para ingresar valores. Así que el filtro no es 100% confiable.
Un enfoque alternativo sería vigilar el evento oninput
, este se dispara después de cualquier modificación. Allí podemos chequear el nuevo input.value
y modificar o resaltar <input>
cuando es inválido. O podemos usar ambos manejadores de eventos juntos.
Código heredado
En el pasado existía un evento keypress
, y también las propiedades del objeto evento keyCode
, charCode
, which
.
Al trabajar con ellos había tantas incompatibilidades entre los navegadores que los desarrolladores de la especificación no tuvieron otra alternativa que declararlos obsoletos y crear nuevos y modernos eventos (los descritos arriba en este capítulo). El viejo código todavía funciona porque los navegadores aún lo soportan, pero no hay necesidad de usarlos más, en absoluto.
Teclados en dispositivos móviles
Cuando se usan teclados virtuales o los de dispositivos móviles, formalmente conocidos como IME (Input-Method Editor), el estándar W3C establece que la propiedad de KeyboardEvent e.keyCode
debe ser 229
y e.key
debe ser "Unidentified"
.
Mientras algunos de estos teclados pueden aún usar los valores correctos para e.key
, e.code
, e.keyCode
…, cuando se presionan ciertas teclas tales como flechas o retroceso no hay garantía, entonces nuestra lógica de teclado podría no siempre funcionar bien en dispositivos móviles.
Resumen
Presionar una tecla siempre genera un evento de teclado, sean teclas de símbolos o teclas especiales como Shift o Ctrl y demás. La única excepción es la tecla Fn que a veces está presente en teclados de laptops. No hay un evento de teclado para ella porque suele estar implementado en un nivel más bajo que el del sistema operativo.
Eventos de teclado:
keydown
– al presionar la tecla (comienza a autorepetir si la tecla queda presionada por un tiempo),keyup
– al soltar la tecla.
Principales propiedades de evento de teclado:
code
– el código de tecla “key code” ("KeyA"
,"ArrowLeft"
y demás), especifica la ubicación física de la tecla en el teclado.key
– el carácter ("A"
,"a"
y demás). Para las teclas que no son de caracteres como Esc, suele tener el mismo valor quecode
.
En el pasado, los eventos de teclado eran usados para detectar cambios en los campos de formulario. Esto no es confiable, porque el ingreso puede venir desde varias fuentes. Para manejar cualquier ingreso tenemos los eventos input
y change
(tratados en el capítulo Eventos: change, input, cut, copy, paste). Ellos se disparan después de cualquier clase de ingreso, incluyendo copiar/pegar y el reconocimiento de voz.
Deberíamos usar eventos de teclado solamente cuando realmente queremos el teclado. Por ejemplo, para reaccionar a atajos o a teclas especiales.