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 startet
click
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 .