Cómo escribir la interfaz de usuario (UI) de PlayStation 5 en JavaScript

Demostración interactiva de PS5.js



Aquí hay una demostración de la interfaz de usuario de PS5 creada con animaciones de JavaScript y CSS que escribiremos en este tutorial. Se puede tocar un ejemplo interactivo en el artículo original .





Coloque un asterisco o forknite project ps5.js 35.9 KB en GitHub.



Escribí un tweet sobre la demostración de PS3 cuando estaba creando la versión básica de la interfaz de usuario de la consola PS 3 en JavaScript . Todavía no tengo el código, pero planeo publicarlo. Además, este tutorial se basa en los conocimientos adquiridos durante la creación del primer trabajo.



Capacitación



Para no complicarnos la vida, no usaremos ningún framework.



Pero incluso si usa marcos o bibliotecas, aún necesita desarrollar su propio patrón para resolver el problema. En este tutorial de IU, lo guiaré a través del concepto mismo detrás del desarrollo. Este enfoque se puede adaptar fácilmente a React, Vue o Angular.



Usé este archivo HTML de plantilla con estilos de flex prediseñados. Contiene todo lo que necesita y la estructura general de la aplicación para comenzar. Esto no es React o Vue, pero esta es la configuración mínima requerida para crear una aplicación. Utilizo este espacio en blanco cada vez que necesito comenzar a trabajar en una nueva aplicación o sitio web vanilla.



HTML y CSS



En esta sección, explicaré algunos de los conceptos básicos de la copia de seguridad de un archivo HTML.



Marco CSS simple de bricolaje



No soy un gran fanático de los frameworks CSS y prefiero empezar desde cero. Sin embargo, después de miles de horas de codificación, comienza a notar patrones que se repiten con frecuencia de todos modos. ¿Por qué no crear algunas clases simples para cubrir los casos más comunes? Esto nos impide escribir los mismos nombres y valores de propiedad cientos de veces.



.rel { position: relative }
.abs { position: absolute }

.top { top: 0 }
.left { left: 0 }
.right { right: 0 }
.bottom { bottom: 0 }

/* flex */
.f { display: flex; }
.v { align-items: center }
.vs { align-items: flex-start }
.ve { align-items: flex-end }
.h { justify-content: center }
.hs { justify-content: flex-start }
.he { justify-content: flex-end }
.r { flex-direction: row }
.rr { flex-direction: row-reverse }
.c { flex-direction: column }
.cr { flex-direction: column-reverse }
.s { justify-content: space-around }

.zero-padding { padding: 0 }

.o { padding: 5px }
.p { padding: 10px }
.pp { padding: 20px }
.ppp { padding: 30px }
.pppp { padding: 50px }
.ppppp { padding: 100px }

.m { margin: 5px }
.mm { margin: 10px }
.mmm { margin: 20px }
.mmmm { margin: 30px }
      
      





Estas clases de CSS hablan por sí solas.



Nuestros primeros estilos CSS



Ahora que tenemos una configuración básica de CSS, agreguemos algunos estilos para cambiar la apariencia de los contenedores de menú ocultos y mostrados. Recuerde que dado que tenemos muchos menús y podemos alternar entre ellos, necesitamos indicar de alguna manera qué menús están "activados" y cuáles están "desactivados".



Por múltiples menús, me refiero a que cada menú tiene su propia pantalla, definida por un elemento HTML separado. Al cambiar al siguiente menú, el contenedor anterior se oculta y se muestra el nuevo. Las transiciones CSS también se pueden usar para crear transiciones suaves de UX, cambiando la opacidad, la posición y la escala.



Todos los contenedores con una clase .menu



predeterminada estarán en el estado "desactivado" (es decir, ocultos). Cualquier elemento con clases .menu



y .current



estará en el estado "encendido" y se mostrará en la pantalla.



Otros elementos, como los botones seleccionables en el menú, utilizan la clase ellos mismos .current



, pero en un contexto diferente de la jerarquía CSS. Exploraremos sus estilos CSS en las siguientes partes del tutorial.



#ps5 {
   width: 1065px;
   height: 600px;
   background: url('https://semicolon.dev/static/playstation_5_teaser_v2.jpg');
   background-size: cover;
}

/* default menu container - can be any UI screen */
#ps5 section.menu {
    display: none;
    opacity: 0;

    // gives us automatic transitions between opacities
    // which will create fade in/fade out effect.
    // without writing any additional JavaScript
    transition: 400ms;      
}

#ps5 section.menu.current {
    display: flex;
    opacity: 1;
}
      
      





section.menu



es de nuevo el contenedor principal estándar para todas las capas de menú que creamos. Esta podría ser la pantalla del "navegador de juegos" o la pantalla de "configuración". Es invisible por defecto hasta que aplicamos la classlist



clase a la propiedad del elemento .current



.



A section.menu.current



indica el menú seleccionado actualmente. ¡Todos los demás menús deben ser invisibles y la clase .current



nunca debe aplicarse a más de un menú al mismo tiempo!



HTML



Nuestro pequeño marco CSS hecho en casa simplifica mucho el HTML. Aquí está el esqueleto principal:



<body>
    <section id = "ps5" class = "rel">
        <section id = "system" class = "menu f v h"></section>
        <section id = "main" class = "menu f v h"></section>
        <section id = "browser" class = "menu f v h"></section>
        <section id = "settings" class = "menu f v h"></section>
    </section>
</body>
      
      





Un elemento ps5



es el contenedor principal de la aplicación.



La parte principal flex



es f v h



para centrar los elementos, por lo que veremos esta combinación a menudo.



También nos encontraremos en f r



lugar de flex-direction:row;



y en f c



lugar de flex-direction:column;



.



Las subsecciones son áreas separadas de un menú que requieren una clase menu



. Podemos cambiar entre ellos.



En el código, serán enumerados por el objeto congelado (lo veremos a continuación).



Reemplazo del fondo



Una de las primeras tareas con las que quería ocuparme era la función de cambio de fondo. Si puedo implementarlo primero, lo integraré más tarde en todas las funciones futuras que necesiten cambiar el fondo. Para ello, decidí crear dos div



.



Cuando el nuevo fondo se activa, simplemente cambio dos div



, reemplazando el valor de la propiedad style.background



con la URL de la nueva imagen, y aplico una clase al nuevo fondo .fade-in



, eliminándolo del anterior.



Empecé con el siguiente CSS:



#background-1, #background-2 {
    position: absolute;
    top: 0;
    left: 0;
    width: inherit;
    height: inherit;
    background: transparent;
    background-position: center center;
    background-size: cover;
    pointer-events: none;
    transition: 300ms;
    z-index: 0;
    opacity: 0;
    transform: scale(0.9)
}

/* This class will be applied from Background.change() function */
.fade-in { opacity: 1 !important; transform: scale(1.0) !important; z-index: 1 }

/* set first visible background */
#background-2 { background-image: url(https://semicolon.dev/static/playstation_5_teaser_v2.jpg); }
      
      





Luego creé una función estática auxiliar .change



que se origina a partir de una clase Background



que intercambia dos div



y los desvanece hacia adentro o hacia afuera (la función toma un argumento, la URL de la siguiente imagen):



class Background {constructor() {}}

Background.change = url => {

    console.log(`Changing background to ${url}`)

    let currentBackground = $(`.currentBackground`);
    let nextBackground = $(`.nextBackground`);

    // set new background to url
    nextBackground.style.backgroundImage = `url(${url})`

    // fade in and out
    currentBackground.classList.remove('fade-in')
    nextBackground.classList.add('fade-in')

    // swap background identity
    currentBackground.classList.remove('currentBackground')
    currentBackground.classList.add('nextBackground')
    nextBackground.classList.remove('nextBackground')
    nextBackground.classList.add('currentBackground')
    
}
      
      





Ahora, cada vez que necesite mostrar un nuevo fondo, simplemente llamaré a esta función con la URL de la imagen que se mostrará:



Background.change('https://semicolon.dev/static/background-1.png')
      
      





El fundido se realizará automáticamente porque transform: 300ms



ya se ha aplicado a cada fondo y la clase .fade-in



está haciendo el resto.



Cómo crear el menú de navegación principal



Ahora que el marco básico está listo, podemos comenzar a construir el resto de la interfaz de usuario. Pero también necesitamos escribir una clase para administrar la interfaz de usuario. Llamemos a esta clase PS5Menu



. Explicaré cómo usarlo a continuación.



Pantalla del sistema



Se utilizó CSS ​​simple para crear el botón Inicio . Tras pulsar el botón por parte del usuario, nos dirigimos al menú principal de PS5. Coloquemos el botón Inicio en el primer menú de la pantalla, en el menú Sistema:



<section id = "system" class = "menu f v h">
    <div id = "start" class = "f v h">Start</div>
</section>
      
      





Asimismo, el contenido de todos los demás menús se ubicará en los elementos contenedores padre correspondientes.



Llegaremos a eso más tarde. Ahora tenemos que averiguar cómo organizar varias pantallas de menú.



En este punto, necesitamos aprender sobre el concepto de poner en cola varios menús. La PS5 tiene varias capas de diferentes interfaces de usuario de navegación. Por ejemplo, cuando selecciona Configuración, se abre un nuevo menú completamente diferente y el control del teclado se transfiere a este nuevo menú.



Necesitamos un objeto para realizar un seguimiento de todos estos menús que se abren, cierran y luego se reemplazan constantemente con un menú nuevo o anterior.



Puedes usar el método incorporado push



Objeto de matriz en JavaScript para agregar un nuevo menú a la cola. Y cuando necesitemos regresar, podemos llamar al método de pop



matriz para regresar al menú anterior.



Enumeramos el menú por atributo de id



elemento:



const MENU = Object.freeze({
    system: `system`,
      main: `main`,
   browser: `browser`,
  settings: `settings`,

/* add more if needed*/

});
      
      





Lo usé Object.freeze()



para que ninguna de las propiedades cambie después de que se establezcan. Es mejor congelar algunos tipos de objetos. Estos son los objetos que definitivamente no deberían cambiar durante la vida de la aplicación.



Aquí, cada valor es el nombre de la propiedad en formato de cadena. De esta manera podemos vincular a los elementos del menú mediante MENU.system



o MENU.settings



. Este enfoque no tiene más que estética sintáctica, y también es una forma sencilla de evitar tener todos los objetos del menú "en una canasta".



Clase PS5Menu



Primero, creé una clase PS5Menu



. Su constructor usa una propiedad de this.queue



tipo Array



.



// menu queue object for layered PS5 navigation
class PS5Menu {

    constructor() {
        this.queue = []
    }

    set push(elementId) {
        // hide previous menu on the queue by removing "current" class
        this.queue.length > 0 && this.queue[this.queue.length - 1].classList.remove(`current`)

        // get menu container
        const menu = $(`#${elementId}`) 

        // make the new menu appear by applying "current" class
        !menu.classList.contains(`current`) && menu.classList.add(`current`)
        
        // push this element onto the menu queue
        this.queue.push( menu ) 

        console.log(`Pushed #${elementId} onto the menu queue`)
    }

    pop() {
        // remove current menu from queue
        const element = this.queue.pop()

        console.log(`Removed #${element.getAttribute('id')} from the menu queue`)
    }
}
      
      





¿Cómo uso la clase PS5Menu?



Esta clase tiene dos métodos, un setter y una función estática . Harán casi lo mismo que los métodos de matriz y lo harán con nuestra matriz . Por ejemplo, para crear una instancia del menú de la clase y agregarla o quitarla del menú de la pila, podemos llamar a métodos y directamente desde una instancia de la clase. push(argument)



pop()



.push()



.pop



this.queue





push



pop







// instantiate the menu object from class
const menu = new PS5Menu()

// add menu to the stack
menu.push = `system`

// remove the last menu that was pushed onto the stack from it
menu.pop()
      
      





Las funciones de establecimiento de clases como esta set push()



no se pueden llamar con ()



. Asignan un valor mediante un operador de asignación =



. La función de establecimiento de clases set push()



se ejecutará con este parámetro.



Combinemos todo lo que ya hemos hecho:



/* Your DOM just loaded */
window.addEventListener('DOMContentLoaded', event => {      

    // Instantiate the queable menu
    const menu = new PS5Menu()

    // Push system menu onto the menu
    menu.push = `system`

    // Attach click event to Start button
    menu.queue[0].addEventListener(`click`, event => {

        console.log(`Start button pressed!`)

        // begin the ps5 demo!
        menu.push = `main`
    });

});
      
      





Aquí hemos creado una instancia de la clase PS5Menu



y almacenado su instancia de objeto en una variable menu



.



Luego, pusimos en cola varios menús con el primer menú con una identificación #system



.



A continuación, adjuntamos un evento al botón Iniciarclick



. Cuando hacemos clic en este botón, hacemos que el menú principal (con id



, igual a main



) sea nuestro menú actual. En este caso, el menú del sistema estará oculto (el menú está actualmente en la cola de menús) y se mostrará el contenedor #menu



.



Tenga en cuenta que, dado que nuestra clase de contenedor de menú .menu.current



tiene la propiedad transform: 400ms;



, luego, con una simple adición o eliminación de una clase .current



de un elemento, las propiedades recién agregadas o eliminadas se animarán en 0.4 milisegundos.



Ahora debes pensar en cómo crear contenido para el menú principal.



Tenga en cuenta que este paso se realiza en el evento DOM "Content Loaded" ( DOMContentLoaded



). Debería ser el punto de entrada para cualquier aplicación de interfaz de usuario. El segundo punto de entrada es un evento window.onload



, pero en esta demostración no lo necesitamos. Espera a que los medios (imágenes, etc.) terminen de descargarse, lo que puede suceder mucho más tarde que los elementos DOM estén disponibles.



Pantalla de bienvenida



Inicialmente, la interfaz de usuario principal es una serie de varios elementos. La fila completa aparece desde el borde derecho de la pantalla. Cuando aparece por primera vez, se anima arrastrándolo hacia la izquierda.



He incrustado estos elementos en el contenedor de #main



esta manera:



<section id = "main" class = "menu f v h">
    <section id = "tab" class = "f">
        <div class = "on">Games</div>
        <div>Media</div>
    </section>
    <section id = "primary" class = "f">
        <div class = "sel t"></div>
        <div class = "sel b current"></div>
        <div class = "sel a"></div>
        <div class = "sel s"></div>
        <div class = "sel d"></div>
        <div class = "sel e"></div>
        <div class = "sel"></div>
        <div class = "sel"></div>
        <div class = "sel"></div>
        <div class = "sel"></div>
        <div class = "sel"></div>
    </section>
</section>
      
      





El primer menú de PS5 se coloca dentro de un contenedor principal, con el siguiente estilo:



#primary {
    position: absolute;
    top: 72px;
    left: 1200px;
    width: 1000px;
    height: 64px;
    opacity: 0;

    /* animate at the rate of 0.4s */
    transition: 400ms;
}

#primary.hidden {
    left: 1200px;
}
      
      





De forma predeterminada, en su estado oculto #primary



, no se muestra intencionalmente; se mueve lo suficiente hacia la derecha (1200px).



Tuvimos que pasar por prueba y error y usar nuestra intuición. Parece que 1200px es una buena opción. Este contenedor también hereda opacity:0



de la clase .menu



.



Entonces, cuando #primary



aparece por primera vez, se desliza y aumenta su brillo al mismo tiempo.



Aquí nuevamente se usa el valor transform:400ms;



(equivalente 0.4s



), porque la mayoría de las microanimaciones se ven bien con 0.4s



. Valor 0.3s



también funciona bien, pero puede ser demasiado rápido y 0.5s



demasiado lento.



Usar transiciones CSS para controlar las animaciones de la interfaz de usuario



En lugar de manipular manualmente los estilos CSS cada vez que necesitamos cambiar el estilo o la posición del bloque de la interfaz de usuario, simplemente podemos asignar y eliminar clases:



// get element:
const element = $(`#primary`)

// check if element already contains a CSS class:
element.style.classList.contains("menu")

// add a new class to element's class list:
element.style.classList.add("menu")

// remove a class from element's class list:
element.style.classList.remove("menu")
      
      





Esta es una estrategia importante que le ahorrará mucho tiempo y mantendrá su código limpio en cualquier proyecto básico. En lugar de cambiar la propiedad, style.left



simplemente eliminaremos la clase .hidden



del elemento #primary



. Como lo ha hecho transform:400ms;



, la animación se reproducirá automáticamente.



Usaremos esta táctica para cambiar casi todos los estados de los elementos de la interfaz de usuario.



Animación secundaria de deslizamiento



Cuando se trabaja con diseño UX, existen diferentes tipos de animaciones. Algunas animaciones se activan al cambiar a un nuevo menú. Por lo general, comienzan después de un corto período de tiempo, poco después de cambiar a una nueva pantalla.



También hay animaciones flotantes que se activan cuando el mouse o el controlador selecciona un nuevo elemento adyacente en el menú de navegación actual.



La atención al detalle es importante, especialmente cuando busca crear un producto de calidad.



Usar la función setTimeout para controlar los estados de la animación



Se reproduce una pequeña animación secundaria a medida que se extraen los elementos . Para simular este doble efecto, se utilizó una función de JavaScript setTimeout



inmediatamente después de que el árbol DOM se cargó por completo.



Dado que esta es la primera pantalla de menú que aparece poco después de hacer clic en el botón Inicio , ahora necesitamos actualizar el evento del click



botón Inicio en el evento DOMContentLoaded justo después menu.push = `main`



.



El siguiente código se ubicará en la parte inferior de una función de evento ya existente DOMContentLoaded



(vea el ejemplo de código fuente que se muestra arriba):



/* Your DOM just loaded */
window.addEventListener('DOMContentLoaded', event => {      

    /* Initial setup code goes here...see previous source code example */

    // Attach click event to Start button
    menu.queue[0].addEventListener(`click`, event => {

        console.log(`Start button pressed!`)

        // begin the ps5 demo!
        menu.push = `main`

        // new code: animate the main UI screen for the first time
        // animate #primary UI block within #main container
        primary.classList.remove(`hidden`)
        primary.classList.add(`current`)

        // animate items up
        let T1 = setTimeout(nothing => {
          
            primary.classList.add('up');

            def.classList.add('current');

            // destroy this timer
            clearInterval(T1)
            T1 = null;

        }, 500)
    });    

});
      
      





¿Qué salió de eso?



Todo el código que escribimos resultó en esta animación inicial:





Crea elementos seleccionables



Ya hemos creado el CSS para los elementos seleccionables (clase .sel



).



Pero todavía se ve rústico, no tan brillante como la interfaz de PS5.



En la siguiente sección, veremos las posibilidades de crear una interfaz más agradable. Elevaremos la interfaz de usuario al aspecto profesional del sistema de navegación PlayStation 5.



Animación estándar del elemento "seleccionado" o "actual"



Tres tipos de animaciones para el elemento seleccionado actualmente



En la interfaz de usuario de la consola PS5, los elementos seleccionados actualmente tienen tres efectos visuales. Un contorno giratorio: un "halo", un punto de luz aleatorio que se mueve en el fondo y, finalmente, una "onda de luz", un efecto que parece una onda que se mueve en la dirección del botón de dirección que se presiona en el controlador. .



En esta sección, aprenderemos cómo crear el clásico efecto de contorno de botón de PS5 con un punto de luz en el fondo y una onda de luz. A continuación se muestra un análisis de cada tipo de animación y las clases de CSS que necesitamos para todos estos tipos:



Halo animado con degradado



Este efecto agrega un borde animado que gira alrededor del elemento seleccionado.



En CSS, esto se puede simular girando un gradiente cónico.



Aquí hay un esquema general de CSS para el elemento seleccionable:



.sel {
    position: relative;
    width: 64px;
    height: 64px;
    margin: 5px;
    border: 2px solid #1f1f1f;
    border-radius: 8px;
    cursor: pointer;
    transition: 400ms;
    transform-style: preserve-3d;
    z-index: 3;
}

.sel.current {
    width: 100px;
    height: 100px;    
}

.sel .under {
    content:'';
    position: absolute;
    width: calc(100% + 8px);
    height: calc(100% + 8px);
    margin: -4px -4px;
    background: #1f1f1f;
    transform: translateZ(-2px);
    border-radius: 8px;
    z-index: 1;
}

.sel .lightwave-container {
    position: relative;
    width: 100%;
    height: 100%;
    transition: 400ms;
    background: black;
    transform: translateZ(-1px);
    z-index: 2;
    overflow: hidden;
}

.sel .lightwave {
    position: absolute;
    top: 0;
    right: 0;
    width: 500%;
    height: 500%;    
    background: radial-gradient(circle at 10% 10%, rgba(72,72,72,1) 0%, rgba(0,0,0,1) 100%);
    filter: blur(30px);
    transform: translateZ(-1px);
    z-index: 2;
    overflow: hidden;
}
      
      





Traté de usar pseudo-elementos ::after



y ::before



, pero no pude lograr los resultados que quiero de manera simple, y su compatibilidad con los navegadores está en duda. Además, JavaScript no tiene una forma nativa de acceder a los pseudoelementos.





En su lugar, decidí crear un nuevo elemento .under



y disminuir su posición Z usando -1 transform: translateZ(-1px)



; por lo tanto, lo alejamos de la cámara, permitiendo que su padre aparezca encima de él.



Es posible que también deba agregar una .sel



propiedad a los elementos principales identificados por el elemento transform-style: preserve-3d;



para habilitar el orden z en el espacio 3D del elemento.



Idealmente, nos gustaría .under



vincular la capa al elemento y crear un punto de luz con el elemento de botón real dentro de él. Pero el truco tiene translateZ



una prioridad más alta, y así fue como comencé a construir la interfaz de usuario. Se puede volver a trabajar, pero no es necesario en esta etapa.



HTML es bastante simple. Lo importante aquí es que ahora tenemos un nuevo elemento .under



. Este es el elemento en el que se renderizará el degradado cónico giratorio para crear un borde brillante sutil.



.lightwave-container



nos ayudará a implementar el efecto de la luz en movimiento overflow: hidden



. .lightwave



- este es el elemento en el que se renderizará el efecto, es un div más grande que va más allá de los bordes del botón y contiene un degradado radial desplazado.



<div id = "o0" data-id = "0" class = "sel b">
    <div class = "under"></div>
    <div class = "lightwave-container">
        <div class = "lightwave"></div>
    </div>
</div>
      
      





A principios de marzo de 2021, las animaciones CSS no admiten la rotación de fondo degradado.



Para solucionar este problema, utilicé una función de JavaScript incorporada window.requestAnimationFrame



. Anima suavemente la propiedad de fondo de acuerdo con la velocidad de fotogramas del monitor, que generalmente es de 60 FPS.



// Continuously rotate currently selected item's gradient border
let rotate = () => {

    let currentlySelectedItem = $(`.sel.current .under`)
    let lightwave = $(`.sel.current .lightwave`)

    if (currentlySelectedItem) {

        let deg = parseInt(selectedGradientDegree);
        let colors = `#aaaaaa, black, #aaaaaa, black, #aaaaaa`;

        // dynamically construct the css style property
        let val = `conic-gradient(from ${deg}deg at 50% 50%, ${colors})`;

        // rotate the border
        currentlySelectedItem.style.background = val

        // rotate lightwave
        lightwave.style.transform = `rotate(${selectedGradientDegree}deg)`;

        // rotate the angle
        selectedGradientDegree += 0.8
    }
    window.requestAnimationFrame(rotate)
}
window.requestAnimationFrame(rotate)
      
      





Esta función es responsable de animar el borde giratorio y el elemento de onda de luz más grande.



El paradigma del oyente de eventos



Dado que no estamos usando React u otros marcos, debemos ocuparnos de los oyentes de eventos nosotros mismos. Cada vez que cambiamos el menú, necesitamos separar todos los eventos del mouse de todos los elementos dentro del contenedor principal del menú anterior y adjuntar detectores de eventos del mouse a todos los elementos interactivos dentro del contenedor principal del nuevo menú seleccionado.



Cada pantalla es única. La forma más sencilla es codificar los eventos de cada pantalla. Esto no es un truco, sino simplemente un código específico para cada sistema de navegación único. Para algunas cosas, simplemente no hay soluciones convenientes.



Las siguientes dos funciones habilitarán y deshabilitarán eventos de diferentes pantallas.



Ver el código fuente completo de PS5.jspara entender cómo funciona todo en general.



function AttachEventsFor(parentElementId) {

    switch (parentElementId) {
        case "system":

          break;
        case "main":

          break;
        case "browser":

          break;
        case "settings":

          break;
    }
}

function RemoveEventsFrom(parentElementId) {

    switch (parentElementId) {
        case "system":

          break;
        case "main":

          break;
        case "browser":

          break;
        case "settings":

          break;
    }
}
      
      





Esto asegura que nunca escuchemos más eventos de mouse de los que tenemos para que el código UX se ejecute de manera óptima para cada pantalla de menú individual.



Navegando con el teclado



Los controles de teclado rara vez se utilizan en aplicaciones web y sitios web. Así que creé una biblioteca de teclado vainilla JS que reconoce teclas básicas y le permite simplemente conectar eventos de pulsación de teclas.



Necesitamos interceptar las siguientes claves:



  • Intro o espacio : selecciona el elemento seleccionado actualmente.
  • Izquierda , Derecha , Arriba , Abajo : navegación a través del menú seleccionado actualmente.
  • Escape : cancela el menú en cola actual y regresa al menú anterior.


Puede vincular todas las claves básicas a las variables de la siguiente manera:



// Map variables representing keys to ASCII codes
const [ A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z ] = Array.from({ length: 26 }, (v, i) => 65 + i);

const Delete = 46;
const Shift = 16;
const Ctrl = 17;
const Alt = 18;

const Left = 37;
const Right = 39;
const Up = 38;
const Down = 40;

const Enter = 13;
const Return = 13;
const Space = 32;
const Escape = 27;
      
      





Y luego crea un controlador de eventos de teclado:



function keyboard_events_main_menu(e) {

    let key = e.which || e.keyCode;

    if (key == Left) {
        if (menu.x > 0) menu.x--
    }

    if (key == Right) {
        if (menu.x < 3) menu.x++
    }

    if (key == Up) {
        if (menu.y > 0) menu.y--
    }

    if (key == Down) {
        if (menu.y < 3) menu.y++
    }

}
      
      





Y conéctelo al objeto del documento:



document.body.addEventListener("keydown", keyboard_events_main_menu);
      
      





API de sonido



Todavía trabajando en ello ...



Mientras tanto, puede descargar aquí una biblioteca de API de sonido simple en Vanilla JS.



All Articles