Categorías: Labs

Internacionaliza los controles del teclado

Esta es una traducción del artículo original publicado en el blog de Mozilla Hacks.

Recientemente encontré dos nuevos demos gráficos, y en ambos casos, los controles no funcionaron en mi teclado francés AZERTY.

Existe el fantástico demo tecnológico WebGL 2 After the Flood y el muy lindo Alpaca Peck. Shaw fue lo suficientemente amigable para arreglar el segundo cuando le dije acerca del problema. Resulta que el navegador web expone una API para esto.

Investiguemos más.

Un teclado, muchas distribuciones

Alrededor del mundo, las personas utilizan diferentes distribuciones del teclado. Puedes leer mucho en la página de distribuciones de teclado de Wikipedia, pero aquí trataré de resumir los puntos importantes.

La distribución más conocida y utilizada es QWERTY, que se usa en la mayor parte del mundo:

Teclado QUERTY

También puedes conocer AZERTY, utilizada en algunos países de habla francesa:

Teclado AZERTY

Además, los teclados QWERTZ se utilizan en Alemania y en otros países europeos, y otra alternativa a QWERTY es DVORAK:

Teclado DVORAK

Cada distribución también tiene variantes, especialmente en los símbolos en la fila superior, como también en las teclas de la derecha. Dos teclados de la misma familia de distribución puede que no sean exactamente iguales. Por ejemplo, los teclados QWERTY españoles tienen una tecla especial para la ñ, y los teclados QWERTZ alemanes tienen teclas especiales para ä y ö.

Teclado QWERTZ

Notarás que los teclados tienen esencialmente la misma estructura para todas las distribuciones. En su mayor parte, las teclas están en la misma posición, aunque puedan ser ligeramente reordenadas o ajustadas. Esto se llama distribución mecánica.

Así, un diseño regional se compone de:

  • La distribución visual está físicamente impresa en las teclas.
  • La distribución funcional refiere al software que mapea las teclas físicas a los caracteres.

Esto significa que podemos cambiar la distribución utilizada en el sistema operativo sin cambiar el teclado físico. ¡Son dos cosas diferentes! Algunos usuarios instalarán controladores de distribución mejorados para poder escribir rápido o escribir caracteres específicos más fácilmente. Esto es muy provechoso cuando los caracteres útiles normalmente no están disponibles en la distribución. Por ejemplo, para escribir en francés, puedo llegar muy fácilmente a É, È, Ç o las comillas francesas « y » gracias al controlador que estoy utilizando.

Pero también es útil cuando necesitas escribir un texto en varios idiomas: no tengo el carácter ø en ningún lugar en mi teclado, pero mi controlador me permite escribirlo fácilmente.

¿Qué sucede en la Web?

Bueno, solía ser un completo desastre. Entonces convergimos en un comportamiento multi-navegador bastante apropiado para los teclados QWERTY.

La API a la que nos hemos acostumbrado gira alrededor de los tres eventos: keydown, keypress, y keyup. A keydown y keyup se les llama eventos de tecla (key events) porque son disparados cada vez que un usuario presiona cualquier tecla, mientras que a keypress se la llama evento de carácter (character event) porque se supone que se dispara cuando se envía un carácter como resultado de pulsar una tecla. Todos los navegadores modernos parecen estar de acuerdo en esto, aún si no fuese siempre el caso.

Para esta API antigua, utilizamos las tres propiedades de KeyboardEvent: keyCode, charCode y which. No voy a entrar en muchos detalles aquí, pero créeme cuando te digo que es una pesadilla trabajar con esto:

  • Las propiedades no tienen el mismo significado en el manejo de un evento de tecla (keydown o keyup) frente a un evento de carácter (keypress).
  • Para algunas teclas y eventos, los valores no son consistentes en distintos navegadores, aún para las últimas versiones de los navegadores.
  • keyCode en los eventos de tecla trata de ser amigable internacionalmente – no, en serio – pero falla miserablemente, debido a la falta de una especificación común.

Así que, ¡vamos a ver qué mejoras nos trae la nueva API!

La nueva API, parte de UI Events

UI Events (eventos de interfaz), conocido anteriormente como DOM Level 3 Events, es una especificación W3C en debate desde el 2000. Se sigue debatiendo como un Working Draft (borrador), pero debido a que la mayoría de los navegadores parecen estar de acuerdo, podemos esperar que la especificación pueda seguir adelante como una recomendación. El último borrador de eventos de teclado está ahora disponible en línea.

La nueva API trae dos propiedades muy útiles para un evento KeyboardEvent: key y code. Reemplazan a las ya pre-existentes (y aún existentes) charCode, keyCode, y which.

Veamos por qué estos cambios son tan útiles, especialmente para hacer sitios web multi-teclado (si me permiten este neologismo).

KeyboardEvent.key da un carácter imprimible o un string descriptivo

La propiedad key es casi un reemplazo directo del anteriormente usado which, excepto que es un poco más predecible.

Cuando la tecla pulsada es un carácter imprimible, obtienes el carácter en forma de string (en vez de su código ASCII/Windows-1252 para which y keyCode, o código Unicode para charCode).

Cuando la tecla pulsada no es un carácter imprimible (por ejemplo: Backspace, Control, pero también Enter o Tab que en efecto son caracteres imprimibles) obtienes un string descriptivo multi-carácter, como 'Backspace', 'Control', 'Enter', 'Tab'.

Entre los principales y modernos navegadores de escritorio, solo Safari no es aún compatible, pero lo será en la próxima versión.

Soporte de KeyboardEvent.key

KeyboardEvent.code te da la tecla física

La propiedad es completamente nueva con esta especificación, aunque es lo que keyCode debería haber sido.

Te da, en forma de string, la tecla física que fue pulsada. Lo que significa que es totalmente independiente de la distribución del teclado que está siendo utilizada.

Así que digamos que el usuario pulsa la tecla Q en un teclado QWERTY. Entonces event.code te da 'KeyQ' mientras que event.key te da 'q'.

Pero cuando un usuario que utiliza teclado AZERTY pulsa la tecla A, también obtiene 'KeyQ' como event.code, mientras que event.key contiene 'a'. Esto sucede porque la tecla A en un teclado AZERTY está en el mismo lugar que la tecla Q en un teclado QWERTY.

En cuanto a los números, la barra superior de dígitos produce valores como 'Digit1', mientras que el teclado numérico produce valores como 'Numpad1'.

Desafortunadamente esta característica sólo está implementada en Blink y Firefox actualmente, pero la compatibilidad con Safari llegará pronto.

Soporte de KeyboardEvent.code

El teclado de referencia

Si cada tecla dispara un código específico…, entonces puedo escuchar tu siguiente pregunta. ¿Qué código es disparado para qué tecla? ¿Cómo es el teclado de referencia?

Esto es más complicado de lo que parece. No existe ningún teclado con todas las teclas posibles.

Es por eso que el W3C publicó una especificación sólo para esto. Puedes leer sobre las distribuciones mecánicas existentes en todo el mundo, así como también su teclado de referencia. Por ejemplo aquí está su teclado de referencia para la parte alfanumérica:

Códigos de teclado

Te animo a echar un vistazo y tener al menos una visión general de esta especificación.

También nota que el W3C ha publicado una especificación hermana que describe los valores para la propiedad key.

La relación entre teclas y códigos

Recomiendo fuertemente revisar los ejemplos dados en la especificación. Ellos demuestran muy claro lo que pasa cuando un usuario presiona varios tipos de teclas, tanto para code como para key.

Controles multi-navegador

El maravilloso Mozilla Developer Network ofrece un buen ejemplo de cómo controlar un juego utilizando WASD o flechas. Pero el ejemplo no ejecuta en múltiples navegadores, y en particular, no funciona en Safari o en Internet Explorer porque ellos no han implementado aún la especificación. Así que miremos cómo podemos soportar código multi-navegador.

Por supuesto, cuando la especificación no está implementada, no funciona correctamente en un teclado que no sea QWERTY. Por esta razón, también es una buena idea utilizar las flechas, porque siempre están en el mismo lugar en todas partes. En este ejemplo, también utilizo el teclado numérico y las teclas IJKL, ya que es menos probable que estén en diferentes lugares.

Aquí hay un ejemplo de cómo un código JavaScript es compatible tanto con la nueva y con la vieja API.

window.addEventListener('keydown', function(e) {
  if (e.defaultPrevented) {
    return;
  }
 
  // No queremos meternos con los atajos del navegador
  if (e.ctrlKey || e.altKey || e.metaKey || e.shiftKey) {
    return;
  }
 
  // Tratamos de usar `code` primero porque es independiente de la distribución.
  // Luego usamos `key` porque algunos navegadores como Internet Explorer y
  // Edge lo soportan pero no `code`. Luego usamos `keyCode` para soportar
  // navegadores más viejos como Safari, Internet Explorer antiguo y Chrome antiguo.
  switch (e.code || e.key || e.keyCode) {
    case 'KeyW': // Es 'W' en teclados QWERTY, 'Z' en teclados AZERTY
    case 'KeyI':
    case 'ArrowUp':
    case 'Numpad8':
    case 38: // keyCode para la flecha hacia arriba
      changeDirectionUp();
      break;
 
    // ... Otras teclas: ASD, JKL, flechas, teclado numérico
 
    default:
      return;
  }
 
  e.preventDefault();
  doSomethingUseful();
});
 
// manejo de tacto
// Una implementación real querría usar touchstart y touchend también.
window.addEventListener('touchmove', function(e) {
  // no olvida pausar el evento
});

¡Prueba la versión completa!

¿Qué falta?

La API por sí sola está bastante bien hecha, no hace falta mucho por hacer.

Sin embargo, aún me falta algo. No hay manera de saber cuál es la distribución del teclado actual. Esto sería realmente útil para escribir instrucciones para controlar el juego: presiona WASD/ZQSD/... dependiendo de la distribución.

También sería útil una API para saber qué letra está detrás de una tecla específica. Pero no sé con certeza si los sistemas operativos subyacentes ofrecen los llamados de bajo nivel necesarios para proporcionar esa información.

Otras cosas útiles

Sin entrar en demasiados detalles, vamos a volar sobre otras funcionalidades significantes en la API:

  • El evento keypress está obsoleto (deprecated). En su lugar, ahora deberías siempre utilizar keydown. El evento beforeinput también está planificado, pero no es compatible a la fecha con ninguna versión estable de un navegador (Chrome Canary tiene una implementación). El evento input es un evento de alto nivel compatible con todos los navegadores que también es útil en algunas situaciones.
  • Con la propiedad location en KeyboardEvent, si una tecla pulsada existe en diferentes lugares – por ejemplo, las teclas Shift o Ctrl, o los dígitos -, luego puedes saber cuál fue usada realmente. Por ejemplo, puedes saber si la tecla presionada está en el teclado numérico o en la barra superior de dígitos. Nota: esta información también está en la propiedad code, como cada tecla física tiene su propio code.
  • La propiedad repeat tiene un valor true si el usuario mantiene presionada una tecla y como resultado se envía un evento repetidas veces.
  • Si deseas saber si una tecla modificadora está presionada mientras manejas el KeyboardEvent de otra tecla, no necesitas mantener el control del estado por ti mismo. Las propiedades booleanas altKey, ctrlKey, metaKey, shiftKey, así como también el método getModifierState, pueden dar el estado de varias teclas modificadoras cuando se disparó el evento.

Aunque parezca extraño, los eventos del teclado no parecen funcionar apropiadamente en plataformas móviles (sin probar en iPhone). ¡Así que asegúrate de tener una interfaz táctil también!

Puedes utilizarla ahora

Esta es mi conclusión: ¡puedes utilizarla ahora! Es posible mejorar progresivamente el código de control de juegos aprovechando la API más reciente para los navegadores modernos, manteniendo al mismo tiempo compatibilidad con navegadores antiguos.

Los usuarios internacionales estarán agradecidos por esto… al utilizar tu producto 🙂

The following two tabs change content below.

Carlos Chocobar