Podstawowy przegląd tworzenia responsywnego, elastycznego i dostępnego menu gry 3D.
W tym poście chcę podzielić się z Tobą sposobem na tworzenie 3D komponentów menu gry. Wypróbuj wersję demonstracyjną.
Jeśli wolisz film, oto wersja tego posta w YouTube:
Omówienie
Gry wideo często oferują użytkownikom kreatywne i niezwykłe menu, które jest animowane i wyświetlane w przestrzeni 3D. W przypadku nowych gier AR/VR popularne jest wyświetlanie menu w przestrzeni. Dziś przypomnimy podstawy tego efektu, ale uzupełnimy go o adaptacyjną kolorystykę i dostosowujemy go do potrzeb użytkowników, którzy preferują ograniczenie ruchu.
HTML
Menu gry to lista przycisków. Najlepszy sposób przedstawienia tego w HTML:
<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>
Lista przycisków będzie dobrze odczytywana przez czytniki ekranu i działa bez JavaScriptu ani CSS.
CSS
Styl listy przycisków dzieli się na te ogólne kroki:
- Konfigurowanie właściwości niestandardowych.
- Układ Flexbox.
- Przycisk niestandardowy z ozdobnymi pseudoelementami.
- Umieszczanie elementów w przestrzeni 3D.
Omówienie właściwości niestandardowych
Własne właściwości pomagają rozróżniać wartości, nadając im znaczące nazwy, dzięki czemu unikniesz powtarzania kodu i dzielenia wartości między elementy potomne.
Poniżej znajdują się zapytania dotyczące multimediów zapisane jako zmienne w usłudze porównywania cen, czyli niestandardowe multimedia. Są one globalne i będą używane w różnych selektorach, aby kod był zwięzły i czytelny. Komponent menu gry korzysta z preferencji dotyczących animacji, schematu kolorów systemu oraz zakresu kolorów wyświetlacza.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);
Te właściwości niestandardowe zarządzają schematem kolorów i przechowują wartości pozycji kursora, aby menu gry było interaktywne po najechaniu kursorem. Nazywanie właściwości niestandardowych ułatwia czytelność kodu, ponieważ ujawniają one przypadek użycia wartości lub przyjazną nazwę wyniku wartości.
.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);
}
}
}
Tła trójkątne w jasnym i ciemnym motywie
Jasny motyw ma żywy gradientcyan
do deeppink
, a ciemny motyw ma ciemny subtelny gradient. Więcej informacji o tym, co można zrobić za pomocą gradientów stożkowych, znajdziesz w artykule conic.style.
html {
background: conic-gradient(at -10% 50%, deeppink, cyan);
@media (--dark) {
background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
}
}
Włączanie perspektywy 3D
Aby elementy mogły istnieć w przestrzeni 3D na stronie internetowej, należy zainicjować widok z perspektywą. Postawiłem perspektywę na element body
i użyłem jednostek widocznego obszaru, by stworzyć styl, który mi się podoba.
body {
perspective: 40vw;
}
To jest rodzaj wpływu, jaki może mieć perspektywa.
Nadawanie stylu liście przycisków <ul>
Ten element odpowiada za ogólny układ makra listy przycisków, a także za interaktywną i wyłanianą kartę 3D. Oto jak to zrobić.
Układ grupy przycisków
Flexbox może zarządzać układem kontenera. Zmień domyślny kierunek elastyczności z wierszy na kolumny za pomocą flex-direction
i upewnij się, że każdy element ma rozmiar swoich treści, zmieniając z stretch
na start
w przypadku align-items
.
.threeD-button-set {
/* remove <ul> margins */
margin: 0;
/* vertical rag-right layout */
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2.5vh;
}
Następnie ustaw kontener jako kontekst przestrzeni 3D i skonfiguruj funkcje CSS clamp()
, aby zapewnić, że karta nie będzie obracana poza czytelne obroty. Zwróć uwagę, że środkowa wartość ograniczenia jest właściwością niestandardową. Te wartości --x
i --y
zostaną ustawione przez JavaScript po późniejszej interakcji myszą.
.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)
)
)
;
}
Następnie, jeśli ruch jest prawidłowy u odwiedzającego użytkownika, dodaj wskazówkę do przeglądarki, że przekształcenie tego elementu będzie się stale zmieniać za pomocą will-change
.
Dodatkowo włącz interpolację, ustawiając wartość transition
w transformacjach. Ten
przejście nastąpi, gdy mysz będzie wchodzić w interakcję z kartą, umożliwiając płynne przejścia między zmianami orientacji. Animacja jest animacją ciągłą, która pokazuje przestrzeń 3D, w której znajduje się karta, nawet jeśli mysz nie może lub nie wchodzi w interakcję z komponentem.
@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;
}
}
Animacja rotate-y
ustawia tylko środkową klatkę kluczową na 50%
, ponieważ przeglądarka domyślnie ustawia wartości 0%
i 100%
na domyślny styl elementu. Jest to skrót do animacji, które się naprzemiennie wyświetlają i muszą zaczynać się i kończyć w tym samym położeniu. To świetny sposób na tworzenie nieskończonych animacji naprzemiennych.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
Stylizacja elementów <li>
Każdy element listy (<li>
) zawiera przycisk i jego elementy obwiedni. Styl display
zostanie zmieniony i element nie wyświetla ::marker
. Styl position
ma wartość relative
, więc pseudoelementy kolejnych przycisków mogą się umieszczać w całym obszarze zajmowanym przez przycisk.
.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;
}
Stylizacja elementów <button>
Nadawanie stylów przyciskom może być trudne, ponieważ trzeba uwzględnić wiele stanów i typów interakcji. Przyciski te szybko się komplikują dzięki równoważeniu pseudoelementów, animacji i interakcji.
Początkowe style <button>
Poniżej znajduje się lista stylów podstawowych, które będą obsługiwane w innych stanach.
.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;
}
Pseudoelementy przycisku
Obramowanie przycisku to nie tradycyjne obramowanie, tylko pseudoelementy o pozycji bezwzględnej z obramowaniem.
Te elementy mają kluczowe znaczenie w prezentowaniu dotychczasowej perspektywy 3D. Jeden z tych pseudoelementów zostanie odsunięty od przycisku, a jeden – bliżej użytkownika. Efekt jest najbardziej zauważalny w przypadku przycisków na górze i na dole.
.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);
}
}
}
Style transformacji 3D
W ustawieniu poniżej transform-style
jest ustawiona wartość preserve-3d
, dzięki czemu dzieci mogą rozmieścić się na osi z
. Wartość transform
jest ustawiona na wartość właściwości niestandardowej --distance
, która będzie zwiększana po najechaniu kursorem i po wybraniu.
.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));
}
}
Style animacji warunkowych
Jeśli użytkownik wyraża zgodę na ruch, przycisk podpowiada przeglądarce, że właściwość przekształcenia powinna być gotowa do zmiany i ustawione jest przejście we właściwościach transform
i background-color
. Zwróć uwagę na różnicę w czasie trwania. Uważam, że dzięki temu udało mi się uzyskać przyjemny, subtelny efekt.
.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 }
}
}
Style interakcji po najechaniu kursorem i po nakierowaniu
Celem animacji interakcji jest rozłożenie warstw, z których składa się przycisk, który pojawia się jako płaski. Aby to zrobić, ustaw zmienną --distance
na 1px
. Selektor pokazany w tym przykładzie kodu sprawdza, czy kursor znajduje się nad przyciskiem lub czy urządzenie, na którym wyświetla się wskaźnik fokusu, nie aktywuje go. Jeśli tak, stosuje ona CSS do:
- Zastosuj kolor tła po najechaniu kursorem.
- Zwiększ dystans .
- Dodaj efekt łagodnego odbicia.
- Rozłóż przejścia pseudoelementów.
.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 }
}
}
}
Perspektywa 3D była nadal bardzo przydatna w przypadku preferencji animacji reduced
.
Elementy górne i dolne prezentują efekt w subtelny sposób.
Małe ulepszenia w języku JavaScript
Interfejs jest już dostępny za pomocą klawiatur, czytników ekranu, kontrolerów, ekranów dotykowych i myszy, ale możemy dodać trochę kodu JavaScript, aby ułatwić kilka scenariuszy.
Obsługa klawiszy strzałek
Klawisz tabulacji sprawdza się w poruszaniu się po menu, ale powinien obsługiwać pad kierunkowy lub joysticki na padzie do gier. Biblioteka roving-ux, która jest często używana do obsługi interfejsów GUI, będzie obsługiwać za nas klawisze strzałek. Podany niżej kod informuje bibliotekę, aby przechwyciła fokus w komponencie .threeD-button-set
i przekazała go do elementów podrzędnych przycisku.
import {rovingIndex} from 'roving-ux'
rovingIndex({
element: document.querySelector('.threeD-button-set'),
target: 'button',
})
Interakcja z paralaksą myszy
Śledzenie myszy i przechylanie menu ma naśladować interfejsy gier wideo AR i VR, w których zamiast myszy może być używany wskaźnik wirtualny. Może być zabawnie, gdy elementy są zbyt świadome wskaźnika.
Ponieważ jest to niewielka dodatkowa funkcja, interakcji będzie towarzyszyć zapytanie o preferencje dotyczące ruchu użytkownika. W ramach konfiguracji przechowuj też komponent listy przycisków w pamięci za pomocą funkcji querySelector
i przechowuj w pamięci podręcznej granice elementu za pomocą funkcji menuRect
. Te granice pozwalają określić opóźnienie obrotu karty w zależności od pozycji myszy.
const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
Następnie potrzebujemy funkcji, która akceptuje pozycje x
i y
myszy oraz zwraca wartość, której możemy użyć, aby obrócić kartę. Funkcja ta korzysta z pozycji myszy, aby określić, po której stronie pola znajduje się mysz i o ile. Funkcja zwraca wartość delta.
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}
}
Na koniec obserwuj ruch kursora, przekaż pozycję do funkcji getAngles()
i użyj wartości delta jako niestandardowych stylów właściwości. Podzieliłem delta przez 20, aby zmniejszyć drgania i to może być lepsze rozwiązanie. Pamiętasz, że na początku umieszczamy elementy --x
i --y
w środku funkcji clamp()
, aby nie można było przesunąć myszy w taki sposób, aby karta znalazła się w nieczytelnej pozycji.
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`)
})
}
Tłumaczenia i wskazówki
Podczas testowania menu gry w innych trybach pisania i językach wystąpiła pewna niedogodność.
Elementy <button>
mają w arkuszu stylów klienta użytkownika styl !important
dla writing-mode
. Oznaczało to, że kod HTML menu gry musiał zostać zmieniony, aby dostosować go do wybranego projektu. Zmiana listy przycisków na listę linków umożliwia zmianę kierunku menu za pomocą właściwości logicznych, ponieważ elementy <a>
nie mają stylu !important
dostarczanego przez przeglądarkę.
Podsumowanie
Skoro już wiesz, jak to robiłem, jak to działa‽ 🙂 Czy możesz dodać do menu interakcję z akcelerometrem, aby ułożenie kafelków na telefonie obróciło menu? Czy możemy poprawić działanie usługi w przypadku braku ruchu?
Zróżnicujemy nasze podejścia i poznamy wszystkie sposoby tworzenia stron internetowych. Utwórz wersję demonstracyjną, wyślij mi linki, a ja dodam je do sekcji z remiksami społeczności.
Remiksy społeczności
Na razie jest tu pusto