Como escrever a interface de usuário (IU) do PlayStation 5 em JavaScript

PS5.js de demonstração interativa



Aqui está uma demonstração da interface do usuário PS5 criada com animações JavaScript e CSS que iremos escrever neste tutorial. Um exemplo interativo pode ser tocado no artigo original .





Coloque um asterisco ou projeto forknite ps5.js 35,9 KB no GitHub.



Escrevi um tweet sobre a demonstração do PS3 quando estava construindo a versão básica da interface do usuário do console PS 3 em JavaScript . Ainda não tenho o código, mas pretendo publicá-lo. Além disso, este tutorial é baseado no conhecimento adquirido durante a criação do primeiro emprego.



Treinamento



Para não complicar nossa vida, não usaremos nenhum framework.



Mas mesmo se você usar estruturas ou bibliotecas, ainda precisa desenvolver seu próprio padrão para resolver o problema. Neste tutorial de IU, irei guiá-lo pelo próprio conceito por trás do desenvolvimento. Essa abordagem pode ser facilmente adaptada para React, Vue ou Angular.



Usei este arquivo HTML de modelo com estilos flex pré-construídos. Ele contém tudo que você precisa e a estrutura geral do aplicativo para começar. Este não é React ou Vue, mas é a configuração mínima necessária para criar um aplicativo. Eu uso esse espaço em branco sempre que preciso começar a trabalhar em um novo aplicativo ou site vanilla.



HTML e CSS



Nesta seção, explicarei alguns dos princípios básicos de criação de um arquivo HTML.



Estrutura simples de CSS DIY



Não sou um grande fã de frameworks CSS e prefiro começar do zero. No entanto, após milhares de horas de codificação, você começa a notar padrões recorrentes de qualquer maneira. Por que não criar algumas classes simples para cobrir os casos mais comuns? Isso nos impede de digitar os mesmos nomes de propriedade e valores centenas de vezes.



.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 }
      
      





Essas classes CSS falam por si.



Nossos primeiros estilos CSS



Agora que temos um CSS básico configurado, vamos adicionar alguns estilos para alterar a aparência dos contêineres de menu ocultos e exibidos. Lembre-se de que, como temos muitos menus e podemos alternar entre eles, precisamos designar de alguma forma quais menus estão “ligados” e quais estão “desligados”.



Por menus múltiplos, quero dizer que cada menu tem sua própria tela, definida por um elemento HTML separado. Ao alternar para o próximo menu, o contêiner anterior é oculto e o novo é exibido. As transições CSS também podem ser usadas para criar transições UX suaves, alterando a opacidade, a posição e a escala.



Todos os contêineres com uma classe .menu



padrão estarão no estado "desligado" (ou seja, ocultos). Qualquer elemento com classes .menu



e .current



estará no estado “ligado” e será exibido na tela.



Outros elementos, como os botões selecionáveis ​​no menu, usam a classe .current



, mas em um contexto diferente da hierarquia CSS. Exploraremos seus estilos CSS nas próximas partes do 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



é novamente o contêiner pai padrão para todas as camadas de menu que criamos. Pode ser a tela do "navegador do jogo" ou a tela de "configurações". É invisível por padrão até que apliquemos a classlist



classe à propriedade do elemento .current



.



A section.menu.current



indica o menu atualmente selecionado. Todos os outros menus devem estar invisíveis e a classe .current



nunca deve ser aplicada a mais de um menu ao mesmo tempo!



Html



Nossa pequena estrutura CSS caseira simplifica muito o HTML. Aqui está o 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>
      
      





Um elemento ps5



é o contêiner principal do aplicativo.



A parte principal flex



é f v h



centralizar os elementos, portanto, veremos essa combinação com frequência.



Também nos encontraremos em f r



vez de flex-direction:row;



e em f c



vez de flex-direction:column;



.



Subseções são áreas separadas de um menu que requerem uma aula menu



. Podemos alternar entre eles.



No código, eles serão enumerados pelo objeto congelado (veremos isso a seguir).



Substituindo o fundo



Uma das primeiras tarefas com que eu queria lidar era a função de mudança de plano de fundo. Se eu puder implementá-lo primeiro, então irei integrá-lo posteriormente em todas as funções futuras que precisam mudar o plano de fundo. Para isso, decidi criar dois div



.



Quando o novo plano de fundo se torna ativo, eu simplesmente troco dois div



, substituindo o valor da propriedade style.background



pelo URL da nova imagem, e aplico uma classe ao novo plano de fundo .fade-in



, removendo-o do anterior.



Comecei com o seguinte 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); }
      
      





Em seguida, criei uma função estática auxiliar .change



que se origina de uma classe Background



que troca dois div



e fade-los dentro ou fora (a função leva um argumento, o URL da próxima imagem):



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')
    
}
      
      





Agora, toda vez que precisar mostrar um novo fundo, simplesmente chamarei esta função com o URL da imagem a ser exibida:



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





O fade in será feito automaticamente porque transform: 300ms



já foi aplicado a cada fundo e a classe .fade-in



está fazendo o resto.



Como criar o menu de navegação principal



Agora que a estrutura básica está pronta, podemos começar a construir o restante da IU. Mas também precisamos escrever uma classe para gerenciar a IU. Vamos chamar essa classe PS5Menu



. Vou explicar como usá-lo abaixo.



Tela do sistema



CSS simples foi usado para criar o botão Iniciar . Após pressionar o botão pelo usuário, vamos para o menu principal do PS5. Vamos colocar o botão Iniciar no primeiro menu da tela - no menu Sistema:



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





Da mesma forma, o conteúdo de todos os outros menus estará localizado nos elementos do contêiner pai correspondente.



Veremos isso mais tarde. Agora precisamos descobrir como organizar várias telas de menu.



Neste ponto, precisamos aprender sobre o conceito de enfileiramento de vários menus. O PS5 possui várias camadas de diferentes interfaces de usuário de navegação. Por exemplo, quando você seleciona Configurações, um novo menu completamente diferente é aberto e o controle do teclado é transportado para esse novo menu.



Precisamos de um objeto para controlar todos esses menus que estão constantemente sendo abertos, fechados e substituídos por um menu novo ou anterior.



Você pode usar o método integrado push



Objeto de matriz em JavaScript para adicionar um novo menu à fila. E quando precisamos retornar, podemos chamar o método pop



array para retornar ao menu anterior.



Listamos o menu por atributo de id



elemento:



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

/* add more if needed*/

});
      
      





Usei Object.freeze()



para que nenhuma das propriedades mudasse depois de configuradas. Alguns tipos de objetos são melhor congelados. Esses são os objetos que definitivamente não devem mudar ao longo da vida do aplicativo.



Aqui, cada valor é o nome da propriedade em formato de string. Dessa forma, podemos vincular os itens do menu por MENU.system



ou MENU.settings



. Não há nada além de estética sintática nessa abordagem, e também é uma maneira simples de evitar o armazenamento de todos os objetos de menu "em uma cesta".



Classe PS5Menu



Primeiro, criei uma classe PS5Menu



. Seu construtor usa uma propriedade 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`)
    }
}
      
      





Como faço para usar a classe PS5Menu?



Esta classe possui dois métodos, um setter e uma função estática . Eles farão quase a mesma coisa que os métodos de array e farão com o nosso array . Por exemplo, para criar uma instância do menu de classe e adicioná-la ou removê-la do menu da pilha, podemos chamar métodos e diretamente de uma instância da classe. 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()
      
      





Funções configuradoras de classe como essa set push()



não podem ser chamadas com ()



. Eles atribuem um valor usando um operador de atribuição =



. A função de configurador de classe set push()



será executada com este parâmetro.



Vamos combinar tudo o que já fizemos:



/* 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`
    });

});
      
      





Aqui, criamos uma instância da classe PS5Menu



e armazenamos sua instância de objeto em uma variável menu



.



Em seguida, colocamos vários menus na fila com o primeiro menu com um id #system



.



Em seguida, anexamos um evento ao botão Iniciarclick



. Quando clicamos neste botão, tornamos o menu principal (com id



, igual a main



) nosso menu atual. Nesse caso, o menu do sistema será oculto (o menu está atualmente na fila de menus) e o contêiner será exibido #menu



.



Observe que, como nossa classe de contêiner de menu .menu.current



tem a propriedade transform: 400ms;



, então, com uma simples adição ou remoção de uma classe .current



de um elemento, as propriedades recém-adicionadas ou removidas serão animadas em 0,4 milissegundos.



Agora você precisa pensar em como criar conteúdo para o menu principal.



Observe que esta etapa é realizada no evento DOM "Content Loaded" ( DOMContentLoaded



). Deve ser o ponto de entrada para qualquer aplicativo de UI. O segundo ponto de entrada é um evento window.onload



, mas nesta demonstração não precisamos dele. Ele aguarda o término do download da mídia (imagens etc.), o que pode acontecer muito depois de os elementos DOM estarem disponíveis.



Tela de abertura



Inicialmente, a IU principal é uma série de vários elementos. A linha inteira aparece na borda direita da tela. Quando aparece pela primeira vez, ele se anima arrastando para a esquerda.



Incorporei esses elementos ao contêiner #main



assim:



<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>
      
      





O primeiro menu PS5 é colocado dentro de um contêiner pai, com o seguinte 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;
}
      
      





Por padrão, em seu estado oculto #primary



, ele não é mostrado intencionalmente; ele é movido o suficiente para a direita (por 1200px).



Tivemos que passar por tentativa e erro e usar nossa intuição. Parece que 1200 px é um bom ajuste. Este contêiner também herda opacity:0



da classe .menu



.



Então, quando ele #primary



aparece pela primeira vez, ele desliza e aumenta seu brilho ao mesmo tempo.



Aqui, novamente, o valor transform:400ms;



(equivalente 0.4s



) é usado, porque a maioria das microanimações fica bem com 0.4s



. Valor 0.3s



também funciona bem, mas pode ser muito rápido ou 0.5s



muito lento.



Usando transições CSS para controlar animações de IU



Em vez de manipular manualmente os estilos CSS sempre que precisarmos alterar o estilo ou a posição do bloco da IU, podemos simplesmente atribuir e remover classes:



// 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 é uma estratégia importante que economizará muito tempo e manterá seu código limpo em qualquer projeto vanilla. Em vez de alterar a propriedade, style.left



vamos apenas remover a classe .hidden



do elemento #primary



. Uma vez que foi transform:400ms;



, a animação será reproduzida automaticamente.



Usaremos essa tática para alterar quase todos os estados dos elementos da IU.



Animação Slide-Out Secundária



Ao trabalhar com design UX, existem diferentes tipos de animações. Algumas animações são acionadas ao alternar para um novo menu. Eles geralmente começam após um curto período de tempo, logo após a mudança para uma nova tela.



Existem também animações de foco que são acionadas quando o mouse ou controlador seleciona um novo item adjacente no menu de navegação atual.



A atenção aos detalhes é importante, especialmente quando você está procurando criar um produto de qualidade.



Usando a função setTimeout para controlar os estados da animação



Uma pequena animação secundária é reproduzida conforme os itens são retirados . Para simular esse efeito duplo, uma função JavaScript foi usada setTimeout



imediatamente depois que a árvore DOM foi totalmente carregada.



Como esta é a primeira tela de menu a aparecer logo após clicar no botão Iniciar , agora precisamos atualizar o evento do click



botão Iniciar no evento DOMContentLoaded logo depois menu.push = `main`



.



O código a seguir ficará na parte inferior de uma função de evento já existente DOMContentLoaded



(consulte o exemplo de código-fonte mostrado acima):



/* 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)
    });    

});
      
      





O que resultou disso



Todo o código que escrevemos resultou nesta animação inicial:





Crie itens selecionáveis



Já criamos o CSS para os elementos selecionáveis ​​(classe .sel



).



Mas ainda parece rústico, não tão brilhante quanto a interface do PS5.



Na próxima seção, veremos as possibilidades de criar uma interface mais agradável. Vamos elevar a interface do usuário à aparência profissional do sistema de navegação PlayStation 5.



Animação padrão do elemento "selecionado" ou "atual"



Três tipos de animações para o item atualmente selecionado



Na interface do usuário do console PS5, os itens atualmente selecionados têm três efeitos visuais. Um contorno giratório - um "halo", um ponto de luz aleatório se movendo no fundo e, finalmente, uma "onda de luz" - um efeito que parece uma onda se movendo na direção do botão de direção pressionado no controlador.



Nesta seção, aprenderemos como criar o efeito clássico de contorno de botão do PS5 com um ponto de luz no fundo e uma onda de luz. Abaixo está uma análise de cada tipo de animação e as classes CSS de que precisamos para todos esses tipos:



Halo animado com gradiente



Este efeito adiciona uma borda animada que gira em torno do item selecionado.



Em CSS, isso pode ser simulado girando um gradiente cônico.



Aqui está um esboço geral de CSS para o elemento selecionável:



.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;
}
      
      





Tentei usar pseudo-elementos ::after



e ::before



, mas não consegui obter os resultados que desejo de maneiras simples, e seu suporte por navegadores está em questão; além disso, o JavaScript não tem uma maneira nativa de acessar pseudoelementos.





Em vez disso, decidi criar um novo elemento .under



e diminuir sua posição Z em -1 usando transform: translateZ(-1px)



; portanto, o movemos para longe da câmera, permitindo que seu pai apareça em cima dela.



Você também pode precisar adicionar uma .sel



propriedade aos elementos pais identificados pelo elemento transform-style: preserve-3d;



para habilitar a ordem z no espaço 3D do elemento.



Idealmente, gostaríamos de .under



direcionar a camada ao elemento e criar um ponto de luz com o elemento de botão real dentro dele. Mas o truque tem translateZ



uma prioridade mais alta e foi assim que comecei a construir a IU. Pode ser retrabalhado, mas nesta fase não é necessário.



HTML é muito simples. O importante aqui é que agora temos um novo elemento .under



. Este é o elemento no qual o gradiente cônico giratório será renderizado para criar uma borda brilhante sutil.



.lightwave-container



nos ajudará a implementar o efeito de mover a luz com overflow: hidden



. .lightwave



- este é o elemento no qual o efeito será renderizado, é um div maior que vai além das bordas do botão e contém um gradiente radial de deslocamento.



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





Desde o início de março de 2021, as animações CSS não são compatíveis com a rotação de fundo gradiente.



Para contornar esse problema, usei uma função JavaScript embutida window.requestAnimationFrame



. Ele anima suavemente a propriedade do plano de fundo de acordo com a taxa de quadros do monitor, que geralmente é 60FPS.



// 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 função é responsável por animar a borda giratória e o elemento de onda de luz maior.



O Paradigma do Listener de Eventos



Como não estamos usando React ou outras estruturas, precisamos lidar com os ouvintes de evento nós mesmos. Cada vez que alternamos o menu, precisamos desanexar todos os eventos do mouse de todos os itens dentro do contêiner pai do menu anterior e anexar ouvintes de eventos do mouse a todos os itens interativos dentro do contêiner pai do novo menu selecionado.



Cada tela é única. A maneira mais fácil é codificar os eventos para cada tela. Este não é um hack, mas simplesmente um código específico para cada sistema de navegação exclusivo. Para algumas coisas, simplesmente não existem soluções convenientes.



As próximas duas funções habilitarão e desabilitarão eventos de telas diferentes.



Veja o código-fonte completo do PS5.jspara entender como tudo funciona em geral.



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;
    }
}
      
      





Isso garante que nunca escutemos mais eventos de mouse do que os que temos, para que o código UX seja executado de maneira ideal para cada tela de menu individual.



Navegar com o teclado



Os controles do teclado raramente são usados ​​em aplicativos da web e sites. Então, criei uma biblioteca de teclado JS vanilla que reconhece teclas básicas e permite que você simplesmente conecte eventos de pressionamento de tecla.



Precisamos interceptar as seguintes chaves:



  • Enter ou Espaço - Seleciona o item atualmente selecionado.
  • Esquerda , Direita , Cima , Baixo - navegação pelo menu selecionado atualmente.
  • Escape - Cancela o menu da fila atual e retorna ao menu anterior.


Você pode vincular todas as chaves básicas a variáveis ​​da seguinte maneira:



// 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;
      
      





Em seguida, crie um manipulador 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++
    }

}
      
      





E conecte-o ao objeto do documento:



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





API de som



Ainda trabalhando nisso ...



Enquanto isso, você pode baixar aqui uma biblioteca API de som simples no vanilla JS.



All Articles