So schreiben Sie die PlayStation 5-Benutzeroberfläche in JavaScript

Interaktive Demo PS5.js



Hier ist eine Demo der PS5-Benutzeroberfläche, die mit JavaScript- und CSS-Animationen erstellt wurde und die wir in diesem Tutorial schreiben werden. Ein interaktives Beispiel kann im Originalartikel berührt werden .





Setzen Sie ein Sternchen oder Forknite-Projekt ps5.js 35.9 KB auf GitHub.



Ich habe einen Tweet über die PS3-Demo geschrieben, als ich die Basisversion der Benutzeroberfläche der PS 3- Konsole in JavaScript erstellt habe . Ich habe den Code noch nicht, aber ich habe vor, ihn zu veröffentlichen. Darüber hinaus baut dieses Tutorial auf den Kenntnissen auf, die Sie beim Erstellen des ersten Jobs erworben haben.



Ausbildung



Um unser Leben nicht zu komplizieren, werden wir keine Frameworks verwenden.



Aber selbst wenn Sie Frameworks oder Bibliotheken verwenden, müssen Sie Ihr eigenes Muster entwickeln, um das Problem zu lösen. In diesem UI-Tutorial werde ich Sie durch das Konzept der Entwicklung führen. Dieser Ansatz kann leicht an React, Vue oder Angular angepasst werden.



Ich habe diese HTML-Vorlagendatei mit vorgefertigten Flex-Stilen verwendet. Es enthält alles, was Sie benötigen, und die allgemeine Struktur der Anwendung, um loszulegen. Dies ist nicht React oder Vue, aber dies ist die Mindestkonfiguration, die zum Erstellen einer Anwendung erforderlich ist. Ich benutze dieses Leerzeichen jedes Mal, wenn ich an einer neuen Vanille-App oder Website arbeiten muss.



HTML und CSS



In diesem Abschnitt werde ich einige der Grundlagen zum Stubben einer HTML-Datei erläutern.



Einfaches DIY CSS Framework



Ich bin kein großer Fan von CSS-Frameworks und beginne lieber bei Null. Nach Tausenden von Stunden Codierung bemerken Sie jedoch ohnehin häufig wiederkehrende Muster. Warum nicht einige einfache Klassen erstellen, um die häufigsten Fälle abzudecken? Dies verhindert, dass wir hunderte Male dieselben Eigenschaftsnamen und -werte eingeben.



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





Diese CSS-Klassen sprechen für sich.



Unsere ersten CSS-Stile



Nachdem wir ein grundlegendes CSS eingerichtet haben, fügen wir einige Stile hinzu, um das Erscheinungsbild von ausgeblendeten und angezeigten Menücontainern zu ändern. Denken Sie daran, dass wir, da wir viele Menüs haben und zwischen ihnen wechseln können, irgendwie festlegen müssen, welche Menüs aktiviert und welche deaktiviert sind.



Mit mehreren Menüs meine ich, dass jedes Menü einen eigenen Bildschirm hat, der durch ein separates HTML-Element definiert wird. Beim Wechsel zum nächsten Menü wird der vorherige Container ausgeblendet und der neue angezeigt. CSS-Übergänge können auch verwendet werden, um glatte UX-Übergänge durch Ändern von Deckkraft, Position und Skalierung zu erstellen.



Alle Container mit einer .menu



Standardklasse befinden sich im Status "Aus" (dh ausgeblendet). Beliebiges Element mit Klassen .menu



und .current



befindet sich im Status "Ein" und wird auf dem Bildschirm angezeigt.



Andere Elemente, wie die im Menü auswählbaren Schaltflächen, verwenden die Klasse selbst .current



, jedoch in einem anderen Kontext der CSS-Hierarchie. Wir werden ihre CSS-Stile in den nächsten Teilen des Tutorials untersuchen.



#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



ist wieder der übergeordnete Standardcontainer für alle von uns erstellten Menüebenen. Dies kann der Bildschirm "Spielbrowser" oder der Bildschirm "Einstellungen" sein. Es ist standardmäßig unsichtbar, bis wir die classlist



Klasse auf die Elementeigenschaft anwenden .current



.



A section.menu.current



zeigt das aktuell ausgewählte Menü an. Alle anderen Menüs dürfen unsichtbar sein und die Klasse .current



darf niemals auf mehrere Menüs gleichzeitig angewendet werden!



Html



Unser hausgemachtes kleines CSS-Framework vereinfacht HTML erheblich. Hier ist das Hauptskelett:



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





Ein Element ps5



ist der Hauptcontainer der Anwendung.



Der Hauptteil flex



dient f v h



zum Zentrieren der Elemente, daher werden wir diese Kombination häufig sehen.



Auch werden wir uns f r



statt flex-direction:row;



und f c



statt treffen flex-direction:column;



.



Unterabschnitte sind separate Bereiche eines Menüs, für die eine Klasse erforderlich ist menu



. Wir können zwischen ihnen wechseln.



Im Code werden sie durch das eingefrorene Objekt aufgelistet (wir werden dies unten sehen).



Hintergrund ersetzen



Eine der ersten Aufgaben, mit denen ich mich befassen wollte, war die Hintergrundänderungsfunktion. Wenn ich es zuerst implementieren kann, werde ich es später in alle zukünftigen Funktionen integrieren, die den Hintergrund ändern müssen. Dafür habe ich beschlossen, zwei zu erstellen div



.



Wenn der neue Hintergrund aktiv wird, tausche ich einfach zwei aus div



, ersetze den Eigenschaftswert style.background



durch die URL des neuen Bildes und wende eine Klasse auf den neuen Hintergrund an .fade-in



, wobei ich ihn aus dem vorherigen entferne.



Ich habe mit folgendem CSS begonnen:



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





Dann habe ich eine statische Hilfsfunktion erstellt .change



, die aus einer Klasse stammt Background



, die zwei vertauscht div



und ein- oder ausblendet (die Funktion verwendet ein Argument, die URL des nächsten Bildes):



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





Jedes Mal, wenn ich einen neuen Hintergrund anzeigen muss, rufe ich diese Funktion einfach mit der URL des anzuzeigenden Bildes auf:



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





Das Einblenden erfolgt automatisch, da es transform: 300ms



bereits auf jeden Hintergrund angewendet wurde und die Klasse .fade-in



den Rest erledigt .



So erstellen Sie das Hauptnavigationsmenü



Nachdem das Grundgerüst fertig ist, können wir mit dem Aufbau der restlichen Benutzeroberfläche beginnen. Wir müssen aber auch eine Klasse schreiben, um die Benutzeroberfläche zu verwalten. Nennen wir diese Klasse PS5Menu



. Ich werde unten erklären, wie man es benutzt.



Systembildschirm



Zum Erstellen der Schaltfläche Start wurde einfaches CSS verwendet. Nach dem Drücken der Taste durch den Benutzer gehen wir zum Hauptmenü der PS5. Platzieren wir die Schaltfläche Start im ersten Menü auf dem Bildschirm - im Menü System:



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





Ebenso befindet sich der Inhalt aller anderen Menüs in den entsprechenden übergeordneten Containerelementen.



Wir werden später darauf zurückkommen. Jetzt müssen wir herausfinden, wie mehrere Menübildschirme organisiert werden.



An dieser Stelle müssen wir etwas über das Konzept des Einreihens mehrerer Menüs lernen. Die PS5 verfügt über mehrere Ebenen mit unterschiedlichen Navigationsoberflächen. Wenn Sie beispielsweise Einstellungen auswählen, wird ein neues, völlig anderes Menü geöffnet, und die Tastatursteuerung wird auf dieses neue Menü übertragen.



Wir benötigen ein Objekt, um all diese Menüs zu verfolgen, die ständig geöffnet, geschlossen und dann durch ein neues oder vorheriges Menü ersetzt werden.



Sie können die integrierte Methode verwenden push



Array-Objekt in JavaScript, um der Warteschlange ein neues Menü hinzuzufügen. Und wenn wir zurückkehren müssen, können wir die pop



Array- Methode aufrufen , um zum vorherigen Menü zurückzukehren.



Wir listen das Menü nach id



Elementattribut auf :



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

/* add more if needed*/

});
      
      





Ich habe verwendet, Object.freeze()



damit sich keine der Eigenschaften ändert, nachdem sie festgelegt wurden. Einige Arten von Objekten werden am besten eingefroren. Dies sind die Objekte, die sich während der gesamten Lebensdauer der Anwendung definitiv nicht ändern sollten.



Hier ist jeder Wert der Name der Eigenschaft im Zeichenfolgenformat. Auf diese Weise können wir über MENU.system



oder auf Menüpunkte verlinken MENU.settings



. Bei diesem Ansatz gibt es nichts anderes als syntaktische Ästhetik, und es ist auch eine einfache Möglichkeit, das Speichern aller Menüobjekte "in einem Korb" zu vermeiden.



PS5Menü-Klasse



Zuerst habe ich eine Klasse erstellt PS5Menu



. Sein Konstruktor verwendet eine this.queue



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





Wie verwende ich die PS5Menu-Klasse?



Diese Klasse verfügt über zwei Methoden, einen Setter und eine statische Funktion . Sie werden fast das Gleiche tun wie Array-Methoden und mit unserem Array . Um beispielsweise eine Instanz des Klassenmenüs zu erstellen und sie zum Menü des Stapels hinzuzufügen oder daraus zu entfernen, können wir Methoden und direkt aus einer Instanz der Klasse aufrufen . 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()
      
      





Klassen-Setter-Funktionen wie diese set push()



können nicht mit aufgerufen werden ()



. Sie weisen einen Wert mit einem Zuweisungsoperator zu =



. Die Klassensetzfunktion set push()



wird mit diesem Parameter ausgeführt.



Kombinieren wir alles, was wir bereits getan haben:



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

});
      
      





Hier haben wir eine Instanz der Klasse erstellt PS5Menu



und ihre Objektinstanz in einer Variablen gespeichert menu



.



Dann haben wir mehrere Menüs mit dem ersten Menü mit einer ID in die Warteschlange gestellt #system



.



Als nächstes haben wir ein Ereignis an die Schaltfläche Start angehängt click



. Wenn wir auf diese Schaltfläche klicken, wird das Hauptmenü (mit id



, gleich main



) zu unserem aktuellen Menü. In diesem Fall wird das Systemmenü ausgeblendet (das Menü befindet sich derzeit in der Menüwarteschlange) und der Container wird angezeigt #menu



.



Beachten Sie, dass unsere Menücontainerklasse .menu.current



die Eigenschaft hat transform: 400ms;



Mit einem einfachen Hinzufügen oder Entfernen einer Klasse zu .current



einem Element werden die neu hinzugefügten oder entfernten Eigenschaften innerhalb von 0,4 Millisekunden animiert.



Jetzt müssen Sie überlegen, wie Sie Inhalte für das Hauptmenü erstellen.



Beachten Sie, dass dieser Schritt im DOM-Ereignis "Content Loaded" ( DOMContentLoaded



) ausgeführt wird. Es sollte der Einstiegspunkt für jede UI-Anwendung sein. Der zweite Einstiegspunkt ist eine Veranstaltung window.onload



, aber in dieser Demo brauchen wir sie nicht. Es wartet, bis die Medien (Bilder usw.) den Download abgeschlossen haben. Dies kann viel später geschehen, als die DOM-Elemente verfügbar sind.



Begrüßungsbildschirm



Anfänglich besteht die Hauptbenutzeroberfläche aus mehreren Elementen. Die gesamte Zeile wird am rechten Bildschirmrand angezeigt. Wenn es zum ersten Mal angezeigt wird, wird es durch Ziehen nach links animiert.



Ich habe diese Elemente folgendermaßen in den Container eingebettet #main



:



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





Das erste PS5-Menü befindet sich in einem übergeordneten Container und sieht wie folgt aus:



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





Standardmäßig wird es in seinem verborgenen Zustand #primary



absichtlich nicht angezeigt, es wird weit genug nach rechts verschoben (um 1200px).



Wir mussten Versuch und Irrtum durchlaufen und unsere Intuition einsetzen. Es sieht so aus, als ob 1200px gut passt. Dieser Container erbt auch opacity:0



von der Klasse .menu



.



Wenn es also #primary



zum ersten Mal angezeigt wird, gleitet es und erhöht gleichzeitig seine Helligkeit.



Auch hier wird der Wert transform:400ms;



(Äquivalent 0.4s



) verwendet, da die meisten Mikroanimationen gut aussehen 0.4s



. Wert 0.3s



funktioniert auch gut, kann aber zu schnell und 0.5s



zu langsam sein.



Verwenden von CSS-Übergängen zur Steuerung von Benutzeroberflächenanimationen



Anstatt CSS-Stile jedes Mal manuell zu bearbeiten, wenn wir den Stil oder die Position des UI-Blocks ändern müssen, können wir einfach Klassen zuweisen und entfernen:



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





Dies ist eine wichtige Strategie, die viel Zeit spart und Ihren Code in jedem Vanille-Projekt sauber hält. Anstatt die Eigenschaft zu ändern, style.left



entfernen wir einfach die Klasse .hidden



aus dem Element #primary



. Da dies transform:400ms;



der Fall ist , wird die Animation automatisch abgespielt.



Wir werden diese Taktik verwenden, um fast jeden Status von UI-Elementen zu ändern.



Sekundäre ausziehbare Animation



Bei der Arbeit mit UX-Design gibt es verschiedene Arten von Animationen. Einige Animationen werden ausgelöst, wenn Sie zu einem neuen Menü wechseln. Sie beginnen normalerweise nach kurzer Zeit, kurz nachdem Sie zu einem neuen Bildschirm gewechselt haben.



Es gibt auch Hover-Animationen, die ausgelöst werden, wenn die Maus oder der Controller ein neues benachbartes Element im aktuellen Navigationsmenü auswählt.



Die Liebe zum Detail ist wichtig, insbesondere wenn Sie ein Qualitätsprodukt erstellen möchten.



Verwenden der Funktion setTimeout zum Steuern von Animationszuständen



Eine kleine sekundäre Animation wird abgespielt, wenn die Elemente herausgezogen werden . Um diesen Doppeleffekt zu simulieren, wurde setTimeout



unmittelbar nach dem vollständigen Laden des DOM-Baums eine JavaScript-Funktion verwendet .



Da dies der erste Menübildschirm wird in Kürze erscheint nach dem Klicken auf Start - Taste , müssen wir jetzt die Aktualisierung startetclick



Taste kurz nach Ereignisse im DOMContentLoaded Ereignisse . Der folgende Code befindet sich am Ende einer bereits vorhandenen Ereignisfunktion (siehe das oben gezeigte Quellcodebeispiel): menu.push = `main`







DOMContentLoaded







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

});
      
      





Was ist daraus geworden?



Der gesamte Code, den wir geschrieben haben, führte zu dieser ersten Animation:





Erstellen Sie auswählbare Elemente



Wir haben bereits das CSS für die auswählbaren Elemente (Klasse .sel



) erstellt.



Aber es sieht immer noch rustikal aus, nicht so glänzend wie die PS5-Oberfläche.



Im nächsten Abschnitt werden wir uns die Möglichkeiten ansehen, eine schönere Oberfläche zu erstellen. Wir werden die Benutzeroberfläche an das professionelle Erscheinungsbild des PlayStation 5-Navigationssystems anpassen.



Standardanimation des "ausgewählten" oder "aktuellen" Elements



Drei Arten von Animationen für das aktuell ausgewählte Element



In der Benutzeroberfläche der PS5-Konsole haben die aktuell ausgewählten Elemente drei visuelle Effekte. Ein rotierender Umriss - ein "Lichthof", ein zufälliger Lichtpunkt, der sich im Hintergrund bewegt, und schließlich eine "Lichtwelle" - ein Effekt, der aussieht wie eine Welle, die sich in Richtung der auf dem Controller gedrückten Richtungstaste bewegt.



In diesem Abschnitt erfahren Sie, wie Sie den klassischen Umriss-Effekt für PS5-Schaltflächen mit einem Lichtpunkt im Hintergrund und einer Lichtwelle erstellen. Nachfolgend finden Sie eine Analyse der einzelnen Animationstypen und der CSS-Klassen, die wir für alle diese Typen benötigen:



Animierter Heiligenschein mit Farbverlauf



Dieser Effekt fügt einen animierten Rahmen hinzu, der sich um das ausgewählte Element dreht.



In CSS kann dies durch Drehen eines sich verjüngenden Gradienten simuliert werden.



Hier ist eine allgemeine CSS-Gliederung für das auswählbare Element:



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





Ich habe versucht , pseudo-Elemente zu verwenden ::after



und ::before



, aber ich konnte die Ergebnisse Ich möchte einfache Wege nicht erreichen, und ihre Unterstützung von Browsern ist in Frage; Darüber hinaus bietet JavaScript keine native Möglichkeit, auf Pseudoelemente zuzugreifen.





Stattdessen habe ich beschlossen, ein neues Element zu erstellen .under



und seine Z-Position mit -1 um -1 zu verringern transform: translateZ(-1px)



. Daher haben wir es von der Kamera wegbewegt, sodass das übergeordnete Element darüber angezeigt werden kann.



Möglicherweise müssen Sie auch übergeordneten Elementen, die durch das Element identifiziert werden, eine .sel



Eigenschaft hinzufügen transform-style: preserve-3d;



, um die Z-Reihenfolge im 3D-Raum des Elements zu aktivieren.



Im Idealfall möchten wir .under



die Ebene dem Element zuordnen und einen Lichtpunkt mit dem eigentlichen Schaltflächenelement darin erstellen. Aber der Trick hat translateZ



eine höhere Priorität, und so habe ich auch angefangen, die Benutzeroberfläche zu erstellen. Es kann überarbeitet werden, ist aber zu diesem Zeitpunkt nicht erforderlich.



HTML ist ziemlich einfach. Wichtig ist hier, dass wir jetzt ein neues Element haben .under



. Dies ist das Element, auf dem der rotierende konische Farbverlauf gerendert wird, um einen subtil leuchtenden Rand zu erzeugen.



.lightwave-container



wird uns helfen, den Effekt des bewegten Lichts mit umzusetzen overflow: hidden



. .lightwave



- Dies ist das Element, auf dem der Effekt gerendert wird. Es ist ein größeres Div, das über die Grenzen der Schaltfläche hinausgeht und einen versetzten radialen Farbverlauf enthält.



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





Ab Anfang März 2021 unterstützen CSS-Animationen keine Hintergrundrotation mit Farbverlauf.



Um dieses Problem zu umgehen, habe ich eine integrierte JavaScript-Funktion verwendet window.requestAnimationFrame



. Die Hintergrundeigenschaft wird reibungslos entsprechend der Monitorbildrate animiert, die normalerweise 60 FPS beträgt.



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





Diese Funktion ist für die Animation des rotierenden Randes und des größeren Lichtwellenelements verantwortlich.



Das Event Listener Paradigma



Da wir weder React noch andere Frameworks verwenden, müssen wir uns selbst mit den Ereignis-Listenern befassen. Jedes Mal, wenn wir das Menü wechseln, müssen wir alle Mausereignisse von allen Elementen im übergeordneten Container des vorherigen Menüs trennen und Listener für Mausereignisse an alle interaktiven Elemente im übergeordneten Container des neu ausgewählten Menüs anhängen.



Jeder Bildschirm ist einzigartig. Am einfachsten ist es, die Ereignisse für jeden Bildschirm fest zu codieren. Dies ist kein Hack, sondern lediglich ein Code, der für jedes einzelne Navigationssystem spezifisch ist. Für einige Dinge gibt es einfach keine bequemen Lösungen.



Die nächsten beiden Funktionen aktivieren und deaktivieren Ereignisse auf verschiedenen Bildschirmen.



Siehe vollständigen PS5.js-Quellcodezu verstehen, wie alles im Allgemeinen funktioniert.



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





Dies stellt sicher, dass wir nie mehr Mausereignisse abhören als bisher, sodass der UX-Code für jeden einzelnen Menübildschirm optimal ausgeführt wird.



Navigieren mit der Tastatur



Tastatursteuerelemente werden in Webanwendungen und Websites selten verwendet. Deshalb habe ich eine Vanilla JS-Tastaturbibliothek erstellt , die grundlegende Tasten erkennt und es Ihnen ermöglicht, einfach Tastendruckereignisse zu verknüpfen.



Wir müssen die folgenden Schlüssel abfangen:



  • Enter oder Space - Wählt das aktuell ausgewählte Element aus.
  • Links , rechts , oben , unten - Navigation durch das aktuell ausgewählte Menü.
  • Escape - Bricht das aktuelle Menü in der Warteschlange ab und kehrt zum vorherigen Menü zurück.


Sie können alle grundlegenden Schlüssel wie folgt an Variablen binden:



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





Erstellen Sie anschließend einen Tastatur-Ereignishandler:



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

}
      
      





Und verbinden Sie es mit dem Dokumentobjekt:



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





Sound API



Ich arbeite noch daran ...



In der Zwischenzeit können Sie hier eine einfache Sound-API-Bibliothek auf Vanilla JS herunterladen .



All Articles