Comment écrire l'interface utilisateur (UI) PlayStation 5 en JavaScript

Démo interactive PS5.js



Voici une démo de l'interface utilisateur PS5 créée avec des animations JavaScript et CSS que nous écrirons dans ce tutoriel. Un exemple interactif peut être touché dans l' article original .





Mettez un astérisque ou un projet forknite ps5.js 35,9 Ko sur GitHub.



J'ai écrit un tweet sur la démo PS3 lorsque je construisais la version de base de l'interface utilisateur de la console PS 3 en JavaScript . Je n'ai pas encore le code, mais je prévois de le publier. De plus, ce tutoriel s'appuie sur les connaissances acquises lors de la création du premier emploi.



Formation



Afin de ne pas nous compliquer la vie, nous n'utiliserons aucun framework.



Mais même si vous utilisez des frameworks ou des bibliothèques, vous devez toujours développer votre propre modèle pour résoudre le problème. Dans ce didacticiel sur l'interface utilisateur, je vais vous expliquer le concept même du développement. Cette approche peut être facilement adaptée à React, Vue ou Angular.



J'ai utilisé ce fichier HTML modèle avec des styles flex prédéfinis. Il contient tout ce dont vous avez besoin et la structure générale de l'application pour commencer. Ce n'est pas React ou Vue, mais c'est la configuration minimale requise pour créer une application. J'utilise ce blanc chaque fois que je dois commencer à travailler sur une nouvelle application ou un nouveau site Web vanilla.



HTML et CSS



Dans cette section, j'expliquerai certaines des bases du stubbing d'un fichier HTML.



Cadre CSS simple de bricolage



Je ne suis pas un grand fan des frameworks CSS et je préfère repartir de zéro. Cependant, après des milliers d'heures de codage, vous commencez quand même à remarquer des modèles récurrents. Pourquoi ne pas créer des classes simples pour couvrir les cas les plus courants? Cela nous empêche de taper des centaines de fois les mêmes noms et valeurs de propriété.



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





Ces classes CSS parlent d'elles-mêmes.



Nos premiers styles CSS



Maintenant que nous avons une configuration CSS de base, ajoutons quelques styles pour changer l'apparence des conteneurs de menu masqués et affichés. N'oubliez pas que puisque nous avons beaucoup de menus et que nous pouvons basculer entre eux, nous devons en quelque sorte désigner quels menus sont «activés» et lesquels sont «désactivés».



Par plusieurs menus, je veux dire que chaque menu a son propre écran, défini par un élément HTML distinct. Lors du passage au menu suivant, le conteneur précédent est masqué et le nouveau s'affiche. Les transitions CSS peuvent également être utilisées pour créer des transitions UX fluides en modifiant l'opacité, la position et l'échelle.



Tous les conteneurs avec une classe .menu



par défaut seront dans l'état «off» (c'est-à-dire masqué). Tout élément avec des classes .menu



et .current



sera à l'état «on» et affiché à l'écran.



D'autres éléments, tels que les boutons sélectionnables dans le menu, utilisent eux-mêmes la classe .current



, mais dans un contexte différent de la hiérarchie CSS. Nous explorerons leurs styles CSS dans les prochaines parties du didacticiel.



#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



est à nouveau le conteneur parent standard pour toutes les couches de menu que nous créons. Cela peut être l'écran «navigateur de jeu» ou l'écran «paramètres». Il est invisible par défaut jusqu'à ce que nous appliquions la classlist



classe à la propriété de l' élément .current



.



A section.menu.current



indique le menu actuellement sélectionné. Tous les autres menus doivent être invisibles et la classe .current



ne doit jamais être appliquée à plus d'un menu à la fois!



HTML



Notre petit framework CSS fait maison simplifie beaucoup le HTML. Voici le squelette 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 élément ps5



est le conteneur principal de l'application.



La partie principale flex



est f v h



de centrer les éléments, nous verrons donc souvent cette combinaison.



Nous nous rencontrerons également f r



au lieu flex-direction:row;



et f c



au lieu de flex-direction:column;



.



Les sous-sections sont des zones distinctes d'un menu qui nécessitent une classe menu



. Nous pouvons basculer entre eux.



Dans le code, ils seront énumérés par l'objet gelé (nous le verrons ci-dessous).



Remplacement de l'arrière-plan



L'une des premières tâches que je voulais traiter était la fonction de changement d'arrière-plan. Si je peux l'implémenter d'abord, je l'intégrerai plus tard dans toutes les futures fonctions qui doivent changer l'arrière-plan. Pour cela, j'ai décidé d'en créer deux div



.



Lorsque le nouvel arrière-plan devient actif, j'échange simplement deux div



, en remplaçant la valeur de propriété style.background



par l'URL de la nouvelle image, et j'applique une classe au nouvel arrière .fade-in



- plan , en le supprimant de la précédente.



J'ai commencé avec le CSS suivant:



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





Ensuite, j'ai créé une fonction statique d'assistance .change



qui provient d'une classe Background



qui en permute deux div



et les fait fondre (la fonction prend un argument, l'URL de l'image suivante):



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





Maintenant, chaque fois que j'ai besoin d'afficher un nouvel arrière-plan, j'appellerai simplement cette fonction avec l'URL de l'image à afficher:



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





Le fondu entrant se fera automatiquement car il transform: 300ms



a déjà été appliqué à chaque arrière-plan et la classe .fade-in



fait le reste.



Comment créer le menu de navigation principal



Maintenant que le cadre de base est prêt, nous pouvons commencer à créer le reste de l'interface utilisateur. Mais nous devons également écrire une classe pour gérer l'interface utilisateur. Appelons cette classe PS5Menu



. Je vais vous expliquer comment l'utiliser ci-dessous.



Écran système



Un CSS simple a été utilisé pour créer le bouton Démarrer . Après avoir appuyé sur le bouton par l'utilisateur, nous allons au menu principal PS5. Plaçons le bouton Démarrer dans le premier menu de l'écran - dans le menu Système:



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





De même, le contenu de tous les autres menus sera situé dans les éléments de conteneur parents correspondants.



Nous y reviendrons plus tard. Nous devons maintenant comprendre comment organiser plusieurs écrans de menu.



À ce stade, nous devons en apprendre davantage sur le concept de mise en file d'attente de plusieurs menus. La PS5 dispose de plusieurs couches de différentes interfaces utilisateur de navigation. Par exemple, lorsque vous sélectionnez Paramètres, un nouveau menu complètement différent s'ouvre et le contrôle du clavier est transféré vers ce nouveau menu.



Nous avons besoin d'un objet pour garder une trace de tous ces menus qui sont constamment ouverts, fermés, puis remplacés par un menu nouveau ou précédent.



Vous pouvez utiliser la méthode intégrée push



Objet Array en JavaScript pour ajouter un nouveau menu à la file d'attente. Et lorsque nous devons revenir, nous pouvons appeler la méthode pop



array pour revenir au menu précédent.



Nous listons le menu par attribut d' id



élément:



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

/* add more if needed*/

});
      
      





J'ai utilisé Object.freeze()



pour qu'aucune des propriétés ne change après leur définition. Certains types d'objets sont mieux gelés. Ce sont les objets qui ne devraient certainement pas changer au cours de la vie de l'application.



Ici, chaque valeur est le nom de la propriété au format chaîne. De cette façon, nous pouvons créer un lien vers des éléments de menu par MENU.system



ou MENU.settings



. Il n'y a rien d'autre qu'une esthétique syntaxique dans cette approche, et c'est aussi un moyen simple d'éviter de stocker tous les objets de menu "dans un même panier".



Classe PS5Menu



Tout d'abord, j'ai créé une classe PS5Menu



. Son constructeur utilise une propriété de this.queue



type 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`)
    }
}
      
      





Comment utiliser la classe PS5Menu?



Cette classe a deux méthodes, un setter et une fonction statique . Ils feront presque la même chose que les méthodes de tableau et le feront avec notre tableau . Par exemple, pour créer une instance du menu de classe et l'ajouter ou la supprimer du menu de la pile, on peut appeler des méthodes et directement depuis une instance de la 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()
      
      





Les fonctions de définition de classe comme celle- set push()



ci ne peuvent pas être appelées avec ()



. Ils attribuent une valeur à l'aide d'un opérateur d'affectation =



. La fonction de définition de classe set push()



s'exécutera avec ce paramètre.



Combinons tout ce que nous avons déjà fait:



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

});
      
      





Ici, nous avons créé une instance de la classe PS5Menu



et stocké son instance d'objet dans une variable menu



.



Ensuite, nous avons mis en file d'attente plusieurs menus avec le premier menu avec un identifiant #system



.



Ensuite, nous avons joint un événement au bouton Démarrerclick



. Lorsque nous cliquons sur ce bouton, nous créons le menu principal (avec id



, égal à main



) notre menu actuel. Dans ce cas, le menu système sera masqué (le menu est actuellement dans la file d'attente des menus) et le conteneur sera affiché #menu



.



Notez que puisque notre classe de conteneur de menu .menu.current



a la propriété transform: 400ms;



, puis avec un simple ajout ou suppression d'une classe .current



d'un élément, les propriétés nouvellement ajoutées ou supprimées s'animeront en 0,4 millisecondes.



Vous devez maintenant réfléchir à la manière de créer du contenu pour le menu principal.



Notez que cette étape est effectuée dans l'événement DOM "Content Loaded" ( DOMContentLoaded



). Il devrait être le point d'entrée de toute application d'interface utilisateur. Le deuxième point d'entrée est un événement window.onload



, mais dans cette démo, nous n'en avons pas besoin. Il attend la fin du téléchargement des médias (images, etc.), ce qui peut arriver beaucoup plus tard que les éléments DOM sont disponibles.



Écran de démarrage



Initialement, l'interface utilisateur principale est une série de plusieurs éléments. La ligne entière apparaît à partir du bord droit de l'écran. Lorsqu'il apparaît pour la première fois, il s'anime en le faisant glisser vers la gauche.



J'ai intégré ces éléments dans le conteneur #main



comme ceci:



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





Le premier menu PS5 est placé dans un conteneur parent, conçu comme suit:



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





Par défaut, dans son état caché #primary



, il n'est intentionnellement pas affiché; il est suffisamment déplacé vers la droite (de 1200 px).



Nous avons dû passer par essais et erreurs et utiliser notre intuition. Il semble que 1200px est un bon ajustement. Ce conteneur hérite également opacity:0



de la classe .menu



.



Ainsi, lorsqu'il #primary



apparaît pour la première fois, il glisse et augmente sa luminosité en même temps.



Ici encore, la valeur transform:400ms;



(équivalente 0.4s



) est utilisée, car la plupart des microanimations ont l'air bien avec 0.4s



. Évaluer 0.3s



fonctionne également bien, mais peut être trop rapide et 0.5s



trop lent.



Utilisation des transitions CSS pour contrôler les animations de l'interface utilisateur



Au lieu de manipuler manuellement les styles CSS chaque fois que nous devons changer le style ou la position du bloc d'interface utilisateur, nous pouvons simplement attribuer et supprimer des 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")
      
      





C'est une stratégie importante qui vous fera gagner beaucoup de temps et gardera votre code propre dans n'importe quel projet vanilla. Au lieu de changer la propriété, style.left



nous allons simplement supprimer la classe .hidden



de l'élément #primary



. Depuis transform:400ms;



, l'animation sera lue automatiquement.



Nous utiliserons cette tactique pour changer presque tous les états des éléments de l'interface utilisateur.



Animation coulissante secondaire



Lorsque vous travaillez avec la conception UX, il existe différents types d'animations. Certaines animations sont déclenchées lors du passage à un nouveau menu. Ils démarrent généralement après une courte période, peu de temps après le passage à un nouvel écran.



Il existe également des animations de survol qui se déclenchent lorsque la souris ou le contrôleur sélectionne un nouvel élément adjacent dans le menu de navigation actuel.



Le souci du détail est important, surtout lorsque vous cherchez à créer un produit de qualité.



Utilisation de la fonction setTimeout pour contrôler les états d'animation



Une petite animation secondaire est lue lorsque les éléments sont retirés . Pour simuler ce double effet, une fonction JavaScript a été utilisée setTimeout



immédiatement après le chargement complet de l'arborescence DOM.



Comme il s'agit du premier écran de menu à apparaître peu de temps après avoir cliqué sur le bouton Démarrer , nous devons maintenant mettre à jour l' événement du click



bouton Démarrer dans l'événement DOMContentLoaded juste après menu.push = `main`



.



Le code suivant sera placé au bas d'une fonction d'événement déjà existante DOMContentLoaded



(voir l'exemple de code source ci-dessus):



/* 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'est-il arrivé



Tout le code que nous avons écrit a abouti à cette animation initiale:





Créer des éléments sélectionnables



Nous avons déjà créé le CSS pour les éléments sélectionnables (classe .sel



).



Mais il a toujours l'air rustique, pas aussi brillant que l'interface PS5.



Dans la section suivante, nous examinerons les possibilités de créer une interface plus agréable. Nous allons élever l'interface utilisateur à l'aspect professionnel du système de navigation PlayStation 5.



Animation standard de l'élément "sélectionné" ou "courant"



Trois types d'animations pour l'élément actuellement sélectionné



Dans l'interface utilisateur de la console PS5, les éléments actuellement sélectionnés ont trois effets visuels. Un contour rotatif - un "halo", une tache de lumière aléatoire se déplaçant en arrière-plan, et enfin, une "onde lumineuse" - un effet qui ressemble à une onde se déplaçant dans la direction du bouton de direction enfoncé sur le contrôleur.



Dans cette section, nous allons apprendre à créer l'effet de contour de bouton PS5 classique avec une tache de lumière en arrière-plan et une onde de lumière. Vous trouverez ci-dessous une analyse de chaque type d'animation et des classes CSS dont nous avons besoin pour tous ces types:



Halo animé avec dégradé



Cet effet ajoute une bordure animée qui tourne autour de l'élément sélectionné.



En CSS, cela peut être simulé en faisant pivoter un dégradé effilé.



Voici un aperçu CSS général de l'élément sélectionnable:



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





J'ai essayé d'utiliser des pseudo-éléments ::after



et ::before



, mais je n'ai pas pu obtenir les résultats souhaités de manière simple, et leur prise en charge par les navigateurs est en question; de plus, JavaScript n'a aucun moyen natif d'accéder aux pseudo-éléments.





Au lieu de cela, j'ai décidé de créer un nouvel élément .under



et de diminuer sa position Z de -1 en utilisant transform: translateZ(-1px)



; ainsi, nous l'avons éloigné de la caméra, permettant à son parent d'apparaître au-dessus.



Vous devrez peut-être également ajouter une .sel



propriété aux éléments parents identifiés par l'élément transform-style: preserve-3d;



pour activer l'ordre z dans l'espace 3D de l'élément.



Idéalement, nous aimerions .under



associer le calque à l'élément et créer un point de lumière avec l'élément de bouton réel à l'intérieur. Mais l'astuce a translateZ



une priorité plus élevée, et c'est aussi ainsi que j'ai commencé à créer l'interface utilisateur. Il peut être retravaillé, mais à ce stade, ce n'est pas nécessaire.



Le HTML est assez simple. L'important ici est que nous avons maintenant un nouvel élément .under



. C'est l'élément sur lequel le dégradé conique rotatif sera rendu pour créer une bordure lumineuse subtile.



.lightwave-container



nous aidera à mettre en œuvre l'effet de la lumière en mouvement avec overflow: hidden



. .lightwave



- c'est l'élément sur lequel l'effet sera rendu, c'est un div plus grand qui dépasse les bordures du bouton et contient un dégradé radial décalé.



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





Depuis début mars 2021, les animations CSS ne prennent pas en charge la rotation d'arrière-plan dégradé.



Pour contourner ce problème, j'ai utilisé une fonction JavaScript intégrée window.requestAnimationFrame



. Il anime en douceur la propriété d'arrière-plan en fonction de la fréquence d'images du moniteur, qui est généralement de 60 images par seconde.



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





Cette fonction est responsable de l'animation de la bordure rotative et du plus grand élément d'onde lumineuse.



Le paradigme de l'auditeur d'événement



Puisque nous n'utilisons pas React ou d'autres frameworks, nous devons gérer nous-mêmes les écouteurs d'événements. Chaque fois que nous changeons de menu, nous devons détacher tous les événements de souris de tous les éléments à l'intérieur du conteneur parent du menu précédent et attacher des écouteurs d'événements de souris à tous les éléments interactifs à l'intérieur du conteneur parent du nouveau menu sélectionné.



Chaque écran est unique. Le moyen le plus simple consiste à coder en dur les événements pour chaque écran. Ce n'est pas un hack, mais simplement un code spécifique à chaque système de navigation unique. Pour certaines choses, il n'y a tout simplement pas de solutions pratiques.



Les deux fonctions suivantes activeront et désactiveront les événements de différents écrans.



Voir le code source complet de PS5.jspour comprendre comment tout fonctionne en général.



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





Cela garantit que nous n'écoutons jamais plus d'événements de souris que nous n'en avons afin que le code UX fonctionne de manière optimale pour chaque écran de menu individuel.



Naviguer avec le clavier



Les commandes au clavier sont rarement utilisées dans les applications Web et les sites Web. J'ai donc créé une bibliothèque de claviers JS vanille qui reconnaît les touches de base et vous permet de connecter simplement des événements de pression de touche.



Nous devons intercepter les clés suivantes:



  • Entrer ou Espace - Sélectionne l'élément actuellement sélectionné.
  • Gauche , Droite , Haut , Bas - navigation dans le menu actuellement sélectionné.
  • Echap - Annule le menu actuellement mis en file d'attente et revient au menu précédent.


Vous pouvez lier toutes les clés de base aux variables comme suit:



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





Et puis créez un gestionnaire d'événements de clavier:



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

}
      
      





Et connectez-le à l'objet document:



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





Son API



Je travaille toujours dessus ...



En attendant, vous pouvez télécharger ici une simple bibliothèque d'API de sons sur vanilla JS.



All Articles