Una web hecha para jugar

¿Qué pasaría si quisieran navegar un catálogo similar al de plataformas como Netflix o la consola Nintendo Switch, utilizando joystick y mouse? Este post trata de cómo combinar tanto mouse como joystick en la navegación de una página web.

Una web hecha para jugar

No todos los días uno se encuentra frente al reto de programar una página web que sea navegable con gamepad, y una vez que empieza a desarrollarlo se va encontrando con distintos desafíos que abarcan desde el diseño hasta el desarrollo.
Hace un tiempo, participé de un proyecto de un catálogo similar al de plataformas como Netflix o la consola Nintendo Switch.

Catálogo de Netflix con su sus distintos programas

Catálogo de la consola Nintendo Switch con sus juegos disponibles

¿Qué pasaría si quisieran navegar un catálogo como estos de ambas maneras, quiero decir, utilizando joystick y mouse?
Bueno, entre los interrogantes a responder tendrían: ¿Cómo navegar entre las distintas secciones? ¿Se puede mover por joystick o mouse? ¿A qué lugar se pueden mover?
Este post trata de cómo combinar tanto mouse como joystick en la navegación de una página web.

Un poco de historia

Actualmente la mayoría de las páginas web son navegadas desde una computadora con un mouse como input principal, y desde hace un par de años el mobile se convirtió en la segunda forma de navegar en la web. Cuando se masificó el uso de los celulares, los programadores tuvimos que adaptar las páginas web existentes para que mantengan un buen nivel de UX1 tanto para los usuarios de mobile, como de pantallas más grandes. A partir de esto es que nació el concepto de mobile-first, ya que escalar desde lo más chico a lo más grande resulta más fácil. Al enfrentarnos a este desafío en particular, en nuestro equipo decidimos trabajar con React y Redux debido a que queríamos tener registrado el estado del sistema en un lugar único, el store. Teniendo en cuenta esta ventaja de diseñar mobile-first, decidimos implementar el mismo concepto con el joystick, “gamepad-first”: primero analizamos cómo se podría comportar la página con gamepad, más restrictivo, y luego lo adaptamos a mouse.

¿A dónde voy y de dónde vengo?

Los distintos inputs, como un mouse o joystick, interpretan el estado de la aplicación de forma diferente. Cuando comparamos el movimiento del mouse con el joystick, si lo interpretamos como si fuera Física, es como considerar que el movimiento del joystick es relativo y depende del estado para saber hacia dónde se está moviendo, mientras que el movimiento del mouse, al ser absoluto, es independiente del estado previo.

Desde el punto de vista del efecto que pueden ocasionar, el mouse solamente se mueve generando un efecto de focusear y hacer click. Mientras que con el joystick el efecto producido depende mucho del significado que le da el programador.
Un ejemplo para el joystick en los videojuegos es el boton “start”. En la pantalla principal puede servir como un “vamos a jugar”, mientras que si se está en medio de una partida puede servir como “menú” o “pausa”.

Conflictos con periféricos

¿Qué pasa si el usuario cambia de navegar con mouse a hacerlo con otro tipo de input, como joystick o teclado? En el siguiente ejemplo utilizo tanto teclado como mouse. Comienzo navegando en Google usando el teclado, y luego apretas tab, lo que me permite cambiar de un elemento focuseado hasta la viñeta de “cartoon”. Posteriormente, usando el mouse bajó hasta la tercer fila de gatitos, y cuando vuelvo al teclado presionando de nuevo ‘tab’ me genera un salto a las categorias de busqueda con ‘fluffy’, para luego moverse  hasta ‘baby’.

Lo que está pasando es que la pagina conoce el estado del elemento focuseado, distinto que donde se encuentra el mouse. Ambos inputs se están ignorando mutuamente, generando al usuario una experiencia molesta, porque se pierde el foco de lo que estaba visualizando. La situación ideal sería que al tabear, cambiara el elemento seleccionado, pero al siguiente de la posición donde en ese momento se encuentra el mouse.

Nuestra solución: Para coordinar el efecto de los distintos inputs, decidimos utilizar un estado compartido en el cual las distintas acciones de los inputs se manejan a través de un lenguaje en común. Esto genera una accionar más armónico.

Otro problema común entre periféricos es que solamente una sección de la pagina deberia reaccionar ante un input. Debido a que el mouse tiene un feedback visual conocido donde depende de donde se encuentre ubicado, el joystick al depender del estado del sistema puede interpretar distinto a lo que visualmente vemos. Si dos secciones consideran que pueden actuar ante una acción, se puede terminar en un estado inconsistente.

React y gamepad

React no está adaptado para reconocer el gamepad, por lo que necesitábamos una librería que reconociera los eventos.React-gamepad wrapea los eventos de HTML5 de gamepad. Aún así, tuvimos el problema de incompatibilidades para algunos joystick donde obtuvimos errores por consola.

Dependiendo del joystick, se mapea de forma diferente la posición fisica y logica. Por lo que la página será compatible solo para un determinado tipo de joystick, o tendremos que adaptar los distintos gamepads.

Joystick de playstation y joystick de nintendo switch

Para el testing nos sirvió mucho que estuviéramos trabajando con la librería de redux. Cada botón emite una o más acciones que cambian el estado del sistema a través de los reducers y esto resulta muy fácil de testear; a partir de un estado inicial, si se realiza una acción, se llega a otro estado final. Incluso podríamos implementar el comportamiento de una pantalla a través de TDD. Por ejemplo si tuviéramos una lista de 3 componentes en fila horizontalmente. Con la condición inicial de que estemos en la posición 0, con la acción “irALaDerecha” no debería cambiar nada del estado, pero si crearamos la acción “irALaIzquierda” debería aumentar en 1 la posición según el estado del store.

Ubicando a los elementos con un orden

En HTML la propiedad TabIndex sirve para darle a los elementos un orden de navegación lógica independientemente de su posición física. Esto desacopla un elemento de los que existen a su alrededor. Cada elemento sabe, dado su índice, a qué otro elemento puede ir.
En nuestro caso no pudimos utilizar la propiedad TabIndex, porque algunos tags directamente no la poseen (por ejemplo el div), son exclusivos de la vista y generan un orden preestablecido que puede ser distinto a como uno quiere navegar la pantalla. Por lo tanto, creamos nuestros propios índices.

Quick-tips:

  • Mapear a teclado: Es más fácil ver los distintos comportamientos con el teclado que a través del joystick, tanto por portabilidad como accesibilidad.
  • Todas las páginas tienen un primer elemento seleccionado determinable y disponible.
  • Elementos sobre otros elementos: Analizar si debería tener prioridad y ser seleccionado como la sección activa.
  • Recordar que cada input puede tener un tipo de movimiento específico: scroll, vibraciones, sensibilidad al presionar un botones, ect.