Eine grundlegende Übersicht zum Erstellen eines responsiven, adaptiven und barrierefreien 3D-Spielmenüs.
In diesem Beitrag möchte ich meine Gedanken zu einer Möglichkeit zum Erstellen einer 3D-Spielmenükomponente teilen. Demo ansehen
Wenn du lieber ein Video ansiehst, findest du hier eine YouTube-Version dieses Beitrags:
Übersicht
In Videospielen wird Nutzern oft ein kreatives und ungewöhnliches Menü präsentiert, das animiert und in einem 3D-Raum ist. In neuen AR-/VR-Spielen ist es beliebt, das Menü so zu gestalten, dass es im Raum schwebt. Heute werden wir die wichtigsten Elemente dieses Effekts nachbilden, aber mit einem adaptiven Farbschema und Optionen für Nutzer, die weniger Bewegung bevorzugen.
HTML
Ein Spielmenü ist eine Liste von Schaltflächen. Am besten wird dies in HTML so dargestellt:
<ul class="threeD-button-set">
<li><button>New Game</button></li>
<li><button>Continue</button></li>
<li><button>Online</button></li>
<li><button>Settings</button></li>
<li><button>Quit</button></li>
</ul>
Eine Liste von Schaltflächen kann von Screenreadern gut erkannt werden und funktioniert ohne JavaScript oder CSS.
CSS
Das Festlegen des Stils für die Schaltflächenliste umfasst die folgenden allgemeinen Schritte:
- Benutzerdefinierte Properties einrichten
- Ein Flexbox-Layout.
- Eine benutzerdefinierte Schaltfläche mit dekorativen Pseudoelementen.
- Elemente im 3D-Raum platzieren
Benutzerdefinierte Properties
Mit benutzerdefinierten Properties lassen sich Werte eindeutig identifizieren, indem ansonsten zufällig erscheinenden Werten aussagekräftige Namen gegeben werden. So wird wiederholter Code vermieden und Werte können für untergeordnete Elemente verwendet werden.
Unten sehen Sie Mediaabfragen, die als CSS-Variablen gespeichert sind, auch als benutzerdefinierte Medien bezeichnet. Sie sind global und werden in verschiedenen Auswahlelementen verwendet, um den Code prägnant und lesbar zu halten. Die Komponente „Spielmenü“ verwendet die Bewegungseinstellungen, das Farbschema des Systems und die Farbbereichsfunktionen des Displays.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);
Mit den folgenden benutzerdefinierten Eigenschaften wird das Farbschema verwaltet und die Mauspositionswerte gespeichert, um das Spielmenü interaktiv zu machen. Die Benennung benutzerdefinierter Properties trägt zur Lesbarkeit des Codes bei, da sie den Anwendungsfall für den Wert oder einen nutzerfreundlichen Namen für das Ergebnis des Werts preisgibt.
.threeD-button-set {
--y:;
--x:;
--distance: 1px;
--theme: hsl(180 100% 50%);
--theme-bg: hsl(180 100% 50% / 25%);
--theme-bg-hover: hsl(180 100% 50% / 40%);
--theme-text: white;
--theme-shadow: hsl(180 100% 10% / 25%);
--_max-rotateY: 10deg;
--_max-rotateX: 15deg;
--_btn-bg: var(--theme-bg);
--_btn-bg-hover: var(--theme-bg-hover);
--_btn-text: var(--theme-text);
--_btn-text-shadow: var(--theme-shadow);
--_bounce-ease: cubic-bezier(.5, 1.75, .75, 1.25);
@media (--dark) {
--theme: hsl(255 53% 50%);
--theme-bg: hsl(255 53% 71% / 25%);
--theme-bg-hover: hsl(255 53% 50% / 40%);
--theme-shadow: hsl(255 53% 10% / 25%);
}
@media (--HDcolor) {
@supports (color: color(display-p3 0 0 0)) {
--theme: color(display-p3 .4 0 .9);
}
}
}
Konische Hintergründe für helle und dunkle Designs
Das helle Design hat einen leuchtenden cyan
bis deeppink
konischen Farbverlauf, während das dunkle Design einen dunklen, subtilen konischen Farbverlauf hat. Weitere Informationen zu den Möglichkeiten mit konischen Farbverläufen finden Sie unter conic.style.
html {
background: conic-gradient(at -10% 50%, deeppink, cyan);
@media (--dark) {
background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
}
}
3D-Perspektive aktivieren
Damit Elemente im 3D-Raum einer Webseite existieren können, muss ein Darstellungsbereich mit einer Perspektive initialisiert werden. Ich habe die Perspektive auf das body
-Element gelegt und Darstellungseinheiten verwendet, um den gewünschten Stil zu erstellen.
body {
perspective: 40vw;
}
Das ist die Art von Auswirkungen, die eine Perspektive haben kann.
<ul>
-Schaltflächenliste stylen
Dieses Element ist für das Gesamtlayout des Makros für die Schaltflächenliste verantwortlich und dient als interaktive und schwebende 3D-Karte. So gehts:
Layout der Schaltflächengruppe
Mit Flexbox kann das Container-Layout verwaltet werden. Ändern Sie die Standardausrichtung von Flex von Zeilen zu Spalten mit flex-direction
und sorgen Sie dafür, dass jedes Element die Größe seines Inhalts hat, indem Sie für align-items
von stretch
zu start
wechseln.
.threeD-button-set {
/* remove <ul> margins */
margin: 0;
/* vertical rag-right layout */
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2.5vh;
}
Legen Sie als Nächstes den Container als 3D-Raumkontext fest und richten Sie CSS-clamp()
-Funktionen ein, damit sich die Karte nicht über lesbare Drehungen hinaus dreht. Der mittlere Wert für die Begrenzung ist eine benutzerdefinierte Eigenschaft. Diese --x
- und --y
-Werte werden später bei der Mausinteraktion über JavaScript festgelegt.
.threeD-button-set {
…
/* create 3D space context */
transform-style: preserve-3d;
/* clamped menu rotation to not be too extreme */
transform:
rotateY(
clamp(
calc(var(--_max-rotateY) * -1),
var(--y),
var(--_max-rotateY)
)
)
rotateX(
clamp(
calc(var(--_max-rotateX) * -1),
var(--x),
var(--_max-rotateX)
)
)
;
}
Wenn der Besucher der Bewegung zustimmt, fügen Sie dem Browser einen Hinweis hinzu, dass sich die Transformation dieses Elements ständig mit will-change
ändert.
Aktivieren Sie außerdem die Interpolation, indem Sie für Transformationen eine transition
festlegen. Dieser Übergang wird ausgeführt, wenn die Maus mit der Karte interagiert. So werden flüssige Übergänge zu Drehungsänderungen ermöglicht. Die Animation läuft kontinuierlich und zeigt den 3D-Raum, in dem sich die Karte befindet, auch wenn eine Maus nicht mit der Komponente interagieren kann oder dies nicht tut.
@media (--motionOK) {
.threeD-button-set {
/* browser hint so it can be prepared and optimized */
will-change: transform;
/* transition transform style changes and run an infinite animation */
transition: transform .1s ease;
animation: rotate-y 5s ease-in-out infinite;
}
}
Bei der rotate-y
-Animation wird nur der mittlere Keyframe auf 50%
festgelegt, da der Browser 0%
und 100%
standardmäßig auf den Standardstil des Elements festlegt. Dies ist eine Kurzschreibweise für Animationen, die sich abwechseln und an derselben Position beginnen und enden müssen. Sie eignet sich hervorragend für endlose abwechselnde Animationen.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
<li>
-Elemente stylen
Jedes Listenelement (<li>
) enthält die Schaltfläche und ihre Rahmenelemente. Der Stil „display
“ wird geändert, sodass für das Element kein ::marker
angezeigt wird. Der Stil position
ist auf relative
festgelegt, damit sich die Pseudoelemente der Schaltfläche im gesamten Bereich positionieren können, den die Schaltfläche einnimmt.
.threeD-button-set > li {
/* change display type from list-item */
display: inline-flex;
/* create context for button pseudos */
position: relative;
/* create 3D space context */
transform-style: preserve-3d;
}
<button>
-Elemente stylen
Das Designen von Schaltflächen kann schwierig sein, da viele Status und Interaktionstypen berücksichtigt werden müssen. Diese Schaltflächen werden schnell komplex, da Pseudoelemente, Animationen und Interaktionen aufeinander abgestimmt werden müssen.
Erste <button>
-Stile
Unten finden Sie die grundlegenden Stile, die die anderen Status unterstützen.
.threeD-button-set button {
/* strip out default button styles */
appearance: none;
outline: none;
border: none;
/* bring in brand styles via props */
background-color: var(--_btn-bg);
color: var(--_btn-text);
text-shadow: 0 1px 1px var(--_btn-text-shadow);
/* large text rounded corner and padded*/
font-size: 5vmin;
font-family: Audiowide;
padding-block: .75ch;
padding-inline: 2ch;
border-radius: 5px 20px;
}
Pseudo-Elemente für Schaltflächen
Die Rahmen der Schaltfläche sind keine herkömmlichen Rahmen, sondern Pseudoelemente mit Rahmen, die sich absolut positionieren lassen.
Diese Elemente sind entscheidend, um die festgelegte 3D-Perspektive zu präsentieren. Eines dieser Pseudoelemente wird von der Schaltfläche weggedrückt und eines näher an den Nutzer herangezogen. Der Effekt ist am deutlichsten bei den oberen und unteren Schaltflächen zu sehen.
.threeD-button button {
…
&::after,
&::before {
/* create empty element */
content: '';
opacity: .8;
/* cover the parent (button) */
position: absolute;
inset: 0;
/* style the element for border accents */
border: 1px solid var(--theme);
border-radius: 5px 20px;
}
/* exceptions for one of the pseudo elements */
/* this will be pushed back (3x) and have a thicker border */
&::before {
border-width: 3px;
/* in dark mode, it glows! */
@media (--dark) {
box-shadow:
0 0 25px var(--theme),
inset 0 0 25px var(--theme);
}
}
}
Stile für 3D-Transformationen
Unten ist transform-style
auf preserve-3d
festgelegt, damit die untergeordneten Elemente auf der z
-Achse gleichmäßig verteilt werden. Die transform
ist auf die benutzerdefinierte Eigenschaft --distance
festgelegt, die beim Bewegen des Mauszeigers und beim Fokus vergrößert wird.
.threeD-button-set button {
…
transform: translateZ(var(--distance));
transform-style: preserve-3d;
&::after {
/* pull forward in Z space with a 3x multiplier */
transform: translateZ(calc(var(--distance) / 3));
}
&::before {
/* push back in Z space with a 3x multiplier */
transform: translateZ(calc(var(--distance) / 3 * -1));
}
}
Bedingte Animationsstile
Wenn der Nutzer mit Bewegungen einverstanden ist, gibt die Schaltfläche dem Browser zu verstehen, dass die Transform-Property geändert werden kann und ein Übergang für die transform
- und background-color
-Properties festgelegt wird. Achten Sie auf die unterschiedlichen Dauern. Ich fand, dass das einen schönen subtilen gestaffelten Effekt erzeugt.
.threeD-button-set button {
…
@media (--motionOK) {
will-change: transform;
transition:
transform .2s ease,
background-color .5s ease
;
&::before,
&::after {
transition: transform .1s ease-out;
}
&::after { transition-duration: .5s }
&::before { transition-duration: .3s }
}
}
Interaktionsstile für Hover- und Fokuseffekte
Ziel der Interaktionsanimation ist es, die Ebenen zu verteilen, aus denen die flach erscheinende Schaltfläche besteht. Legen Sie dazu die Variable --distance
anfangs auf 1px
fest. Mit dem im folgenden Codebeispiel gezeigten Auswahltool wird geprüft, ob die Schaltfläche von einem Gerät angetippt oder fokussiert wird, auf dem ein Fokusindikator angezeigt werden sollte, und nicht aktiviert wird. In diesem Fall wird CSS angewendet, um Folgendes zu tun:
- Wenden Sie die Hintergrundfarbe für den Mauszeiger an.
- Erhöhen Sie den Abstand .
- Fügen Sie einen Bounce-Effekt hinzu.
- Die Pseudoelementübergänge versetzt ausführen
.threeD-button-set button {
…
&:is(:hover, :focus-visible):not(:active) {
/* subtle distance plus bg color change on hover/focus */
--distance: 15px;
background-color: var(--_btn-bg-hover);
/* if motion is OK, setup transitions and increase distance */
@media (--motionOK) {
--distance: 3vmax;
transition-timing-function: var(--_bounce-ease);
transition-duration: .4s;
&::after { transition-duration: .5s }
&::before { transition-duration: .3s }
}
}
}
Die 3D-Perspektive war für die reduced
-Bewegungseinstellung immer noch sehr cool.
Die oberen und unteren Elemente zeigen den Effekt auf dezente Weise.
Kleine Verbesserungen mit JavaScript
Die Benutzeroberfläche kann bereits über Tastaturen, Screenreader, Gamepads, Touchbedienung und Maus verwendet werden. Wir können aber einige JavaScript-Elemente hinzufügen, um einige Szenarien zu vereinfachen.
Unterstützung von Pfeiltasten
Die Tabulatortaste ist eine gute Möglichkeit, sich im Menü zu bewegen. Ich würde jedoch erwarten, dass der Fokus auf einem Gamepad mit dem Steuerkreuz oder dem Joystick verschoben wird. Die Bibliothek roving-ux, die häufig für GUI-Herausforderungen verwendet wird, übernimmt die Pfeiltasten für uns. Der folgende Code weist die Bibliothek an, den Fokus in .threeD-button-set
zu fangen und an die untergeordneten Schaltflächen weiterzuleiten.
import {rovingIndex} from 'roving-ux'
rovingIndex({
element: document.querySelector('.threeD-button-set'),
target: 'button',
})
Maus-Parallaxe-Interaktion
Die Maus wird verfolgt und das Menü wird durch das Neigen der Maus gedreht. Das soll die Benutzeroberfläche von AR- und VR-Videospielen nachahmen, bei denen anstelle einer Maus ein virtueller Cursor verwendet wird. Es kann Spaß machen, wenn Elemente sehr auf den Cursor reagieren.
Da dies eine kleine zusätzliche Funktion ist, binden wir die Interaktion in eine Abfrage der Bewegungspräferenz des Nutzers ein. Speichern Sie außerdem im Rahmen der Einrichtung die Schaltflächenlistenkomponente mit querySelector
im Arbeitsspeicher und die Begrenzungen des Elements in menuRect
. Anhand dieser Grenzen wird der Drehungsoffset bestimmt, der auf die Karte angewendet wird, basierend auf der Mausposition.
const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
Als Nächstes benötigen wir eine Funktion, die die x
- und y
-Positionen der Maus akzeptiert und einen Wert zurückgibt, mit dem wir die Karte drehen können. Bei der folgenden Funktion wird anhand der Mausposition ermittelt, auf welcher Seite des Felds sich die Maus befindet und wie weit. Die Delta wird von der Funktion zurückgegeben.
const getAngles = (clientX, clientY) => {
const { x, y, width, height } = menuRect
const dx = clientX - (x + 0.5 * width)
const dy = clientY - (y + 0.5 * height)
return {dx,dy}
}
Beobachten Sie zuletzt die Mausbewegung, übergeben Sie die Position an die getAngles()
-Funktion und verwenden Sie die Deltawerte als benutzerdefinierte Property-Stile. Ich habe durch 20 geteilt, um die Deltawerte zu glätten und weniger ruckelig zu machen. Es gibt möglicherweise eine bessere Möglichkeit, das zu tun. Wie Sie sich erinnern, haben wir die --x
- und --y
-Props in die Mitte einer clamp()
-Funktion gesetzt. Dadurch wird verhindert, dass die Karte durch die Mausposition zu stark gedreht wird und unlesbar wird.
if (motionOK) {
window.addEventListener('mousemove', ({target, clientX, clientY}) => {
const {dx,dy} = getAngles(clientX, clientY)
menu.attributeStyleMap.set('--x', `${dy / 20}deg`)
menu.attributeStyleMap.set('--y', `${dx / 20}deg`)
})
}
Übersetzungen und Wegbeschreibungen
Beim Testen des Spielmenüs in anderen Schriftarten und Sprachen gab es ein Problem.
<button>
-Elemente haben im User-Agent-Stylesheet einen !important
-Stil für writing-mode
. Das bedeutete, dass das HTML-Spielmenü an das gewünschte Design angepasst werden musste. Wenn Sie die Schaltflächenliste in eine Liste von Links ändern, können Sie mithilfe logischer Eigenschaften die Menürichtung ändern, da <a>
-Elemente keinen vom Browser bereitgestellten !important
-Stil haben.
Fazit
Jetzt, da Sie wissen, wie ich es gemacht habe, wie würden Sie es machen? 🙂 Können Sie dem Menü eine Interaktion mit dem Beschleunigungsmesser hinzufügen, sodass das Menü gedreht wird, wenn Sie Ihr Smartphone schwenken? Können wir die Funktion für Videos ohne Bewegung verbessern?
Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie Sie im Web entwickeln können. Erstelle eine Demo, tweete mir Links und ich füge sie unten in den Abschnitt „Community-Remixe“ hinzu.
Remixe der Community
Noch keine Aktivität hierzu.