Ringkasan dasar tentang cara membangun menu game 3D yang responsif, adaptif, dan mudah diakses.
Dalam postingan ini saya ingin berbagi pemikiran tentang cara membangun komponen menu game 3D. Mulai demo.
Jika Anda lebih suka menonton video, berikut versi YouTube untuk postingan ini:
Ringkasan
{i>Video game<i} sering menampilkan menu yang kreatif dan tidak biasa, dengan animasi dan ruang 3D. Menu ini populer di game AR/VR baru karena menu tampak yang mengambang di angkasa. Hari ini kita akan mendesain ulang hal-hal penting dari efek ini tetapi dengan sentuhan tambahan dari skema warna adaptif dan akomodasi untuk pengguna yang lebih suka mengurangi gerakan.
HTML
Menu game merupakan daftar tombol. Cara terbaik untuk merepresentasikannya dalam HTML adalah dengan berikut ini:
<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>
Daftar tombol akan muncul dengan baik ke teknologi {i>screen reader<i} dan berfungsi tanpa JavaScript atau CSS.
CSS
Menata gaya daftar tombol dibagi menjadi langkah-langkah tingkat tinggi berikut:
- Menyiapkan properti kustom.
- Tata letak flexbox.
- Tombol kustom dengan elemen pseudo dekoratif.
- Menempatkan elemen ke dalam ruang 3D.
Ringkasan properti kustom
Properti kustom membantu membedakan nilai dengan memberikan nama menjadi nilai yang tampak acak, menghindari kode berulang dan berbagi di antara anak-anak.
Di bawah ini adalah kueri media yang disimpan sebagai variabel CSS, yang disebut juga kustom media. Model tersebut bersifat global dan akan digunakan di berbagai pemilih agar kode tetap ringkas dan dapat dibaca. Tujuan komponen menu game menggunakan gerakan preferensi, warna sistem skema, dan rentang warna dari tampilan.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);
Properti khusus berikut mengelola skema warna dan tahan mouse nilai posisi untuk membuat menu game interaktif saat kursor diarahkan ke atasnya. Penamaan kustom properti membantu keterbacaan kode karena mengungkap kasus penggunaan untuk nilai atau nama yang sesuai untuk hasil nilai.
.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);
}
}
}
Latar belakang kerucut latar belakang tema terang dan gelap
Tema terang memiliki kerucut cyan
hingga deeppink
yang cerah
gradien
sedangkan tema gelap memiliki gradien kerucut halus gelap. Untuk mengetahui lebih lanjut tentang
dapat dilakukan dengan gradien kerucut, lihat conic.style.
html {
background: conic-gradient(at -10% 50%, deeppink, cyan);
@media (--dark) {
background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
}
}
Mengaktifkan perspektif 3D
Agar elemen ada dalam ruang 3D laman web, area pandang dengan
perspektif
perlu diinisialisasi. Saya memilih untuk memberikan perspektif pada elemen body
dan menggunakan unit area pandang untuk membuat gaya yang saya sukai.
body {
perspective: 40vw;
}
Inilah jenis perspektif dampak yang dapat ditimbulkan.
Menata gaya daftar tombol <ul>
Elemen ini bertanggung jawab atas keseluruhan tata letak makro daftar tombol serta menjadi kartu mengambang 3D yang interaktif. Berikut cara untuk melakukannya.
Tata letak grup tombol
Flexbox dapat mengelola tata letak penampung. Mengubah arah default flex
dari baris ke kolom dengan flex-direction
dan pastikan setiap item berukuran
isinya dengan mengubah dari stretch
menjadi start
untuk 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;
}
Selanjutnya, tetapkan penampung sebagai konteks ruang 3D dan siapkan clamp()
CSS
fungsi guna memastikan kartu tidak berputar di luar rotasi yang dapat dibaca. Pemberitahuan
bahwa nilai tengah untuk klem adalah properti khusus, --x
dan --y
ini
nilai akan ditetapkan dari JavaScript dengan mouse
interaksi nanti.
.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)
)
)
;
}
Selanjutnya, jika {i>motion <i}diizinkan oleh pengguna yang berkunjung, tambahkan petunjuk ke {i>browser<i} yang
transformasi item ini akan
terus berubah dengan
will-change
Selain itu, aktifkan interpolasi dengan menetapkan transition
pada transformasi. Ini
akan terjadi ketika mouse berinteraksi dengan kartu, sehingga memungkinkan
perubahan rotasi. Animasi adalah animasi yang berjalan secara konstan
yang menunjukkan ruang 3D tempat kartu berada, meskipun {i>mouse<i} tidak dapat atau
tidak berinteraksi
dengan komponen.
@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;
}
}
Animasi rotate-y
hanya menetapkan keyframe tengah pada 50%
sejak
browser akan menetapkan 0%
dan 100%
ke gaya default elemen. Ini
adalah singkatan untuk animasi yang bergantian, harus dimulai dan diakhiri pada saat yang sama
posisi Anda. Ini adalah cara yang bagus untuk menyampaikan animasi alternatif tanpa batas.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
Menata gaya elemen <li>
Setiap item daftar (<li>
) berisi tombol dan elemen batasnya. Tujuan
Gaya display
diubah sehingga item tidak menampilkan
::marker
. Gaya position
disetel ke relative
sehingga elemen pseudo tombol yang akan datang dapat memosisikan
sendiri dalam area penuh
yang dikonsumsi tombol.
.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;
}
Menata gaya elemen <button>
Menata gaya tombol bisa menjadi pekerjaan sulit, ada banyak status dan jenis interaksi yang harus diperhitungkan. Tombol-tombol ini menjadi kompleks dengan cepat karena menyeimbangkan elemen semu, animasi, dan interaksi.
Gaya <button>
awal
Berikut adalah gaya dasar yang akan mendukung status lain.
.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;
}
Elemen pseudo tombol
Batas tombol bukanlah batas tradisional, melainkan posisi mutlak {i>pseudo-element<i} dengan {i>border<i}.
Elemen-elemen ini sangat penting dalam menampilkan perspektif 3D yang telah mapan. Salah satu elemen-pseudo ini akan didorong menjauhi tombol, dan satu item akan ditarik lebih dekat ke pengguna. Efeknya paling terlihat dalam tombol atas dan bawah.
.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);
}
}
}
Gaya transformasi 3D
Di bawah transform-style
disetel ke preserve-3d
sehingga turunan dapat memberi spasi
berada di sumbu z
. transform
disetel ke --distance
properti kustom, yang akan ditingkatkan dengan arah kursor dan
fokus.
.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));
}
}
Gaya animasi bersyarat
Jika pengguna setuju dengan {i>motion-<i}nya, tombol itu memberi petunjuk ke browser bahwa
properti transform harus siap untuk diubah dan transisi diatur untuk
Properti transform
dan background-color
. Perhatikan perbedaan
durasi singkat, saya merasa itu menghasilkan efek bergiliran yang halus dan bagus.
.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 }
}
}
Gaya interaksi arahkan kursor dan fokus
Tujuan dari animasi interaksi adalah untuk menyebarkan {i>layer<i} yang membentuk
muncul datar. Selesaikan ini dengan menetapkan variabel --distance
,
awalnya ke 1px
. Pemilih yang ditampilkan dalam contoh kode berikut memeriksa
melihat apakah tombol diarahkan atau difokuskan oleh perangkat yang seharusnya melihat
indikator fokus, dan tidak diaktifkan. Jika demikian, berlaku CSS untuk melakukan
berikut ini:
- Terapkan warna latar belakang pengarahan kursor.
- Tambah jarak .
- Tambahkan efek kemudahan pantulan.
- Lakukan transisi elemen semu secara bertahap.
.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 }
}
}
}
Perspektif 3D masih sangat rapi untuk preferensi gerakan reduced
.
Elemen atas dan bawah menampilkan efek dengan cara yang halus dan bagus.
Peningkatan kecil dengan JavaScript
Antarmuka ini dapat digunakan dari {i>keyboard<i}, {i>screen reader<i}, {i>gamepad<i}, {i>touch<i} dan {i>mouse<i}, tapi kita dapat menambahkan sedikit sentuhan pada JavaScript untuk memudahkan beberapa skenario.
Mendukung tombol panah
Tombol tab adalah cara baik untuk menavigasi menu, tetapi saya mengharapkan
tombol atau joystick untuk memindahkan fokus pada gamepad. Tujuan
Library roving-ux yang sering digunakan untuk GUI
Antarmuka tantangan akan menangani tombol panah untuk kita. Kode di bawah ini memberi tahu
library untuk menjebak fokus dalam .threeD-button-set
dan meneruskan fokus ke
turunan tombol.
import {rovingIndex} from 'roving-ux'
rovingIndex({
element: document.querySelector('.threeD-button-set'),
target: 'button',
})
Interaksi paralaks mouse
Melacak mouse dan memiringkannya ke menu dimaksudkan untuk meniru AR dan VR antarmuka {i>video game<i}, di mana alih-alih {i>mouse<i} Anda mungkin memiliki pointer virtual. Akan sangat menyenangkan ketika elemen sangat sadar terhadap pointer.
Karena ini adalah fitur tambahan kecil, kita akan menempatkan interaksi di belakang kueri
preferensi {i>motion <i}
pengguna. Selain itu, sebagai bagian dari penyiapan, simpan daftar tombol
komponen ke dalam memori dengan querySelector
dan meng-cache batas elemen ke dalam
menuRect
. Gunakan batas ini untuk menentukan offset rotasi yang diterapkan pada kartu
berdasarkan posisi mouse.
const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
Selanjutnya, kita memerlukan fungsi yang menerima posisi x
dan y
mouse dan menampilkan
nilai yang dapat kita gunakan
untuk memutar kartu. Fungsi berikut menggunakan {i>mouse<i}
posisi untuk menentukan di sisi mana
kotak itu berada dan berapa banyak. Tujuan
delta ditampilkan dari fungsi.
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}
}
Terakhir, perhatikan gerakan mouse, teruskan posisi ke fungsi getAngles()
kita
dan menggunakan nilai delta sebagai gaya properti kustom. saya bagi dengan 20 untuk
delta dan membuatnya tidak terlalu gugup, mungkin ada cara yang lebih baik untuk melakukannya. Jika Anda
ingat sejak awal, kita menempatkan properti --x
dan --y
di tengah
clamp()
, hal ini mencegah posisi mouse terlalu sering memutar
ke posisi yang tidak terbaca.
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`)
})
}
Terjemahan dan arahan
Ada satu hal yang tidak berguna saat menguji menu game dalam mode penulisan lainnya dan bahasa.
Elemen <button>
memiliki gaya !important
untuk writing-mode
di pengguna
stylesheet agen. Artinya, HTML menu {i>game<i} perlu
diubah untuk mengakomodasi
desain yang diinginkan. Mengubah daftar tombol menjadi daftar
tautan memungkinkan logika
properti untuk mengubah arah menu, karena elemen <a>
tidak memiliki browser
gaya !important
yang diberikan.
Kesimpulan
Sekarang setelah Anda tahu cara saya melakukannya, bagaimana Anda‽ 🙂 Bisakah Anda menambahkan akselerometer pengguna ke menu, jadi memutar menu akan menggunakan ponsel Anda? Bisakah kita memperbaiki pengalaman tanpa gerakan?
Mari kita diversifikasi pendekatan kami dan mempelajari semua cara untuk membangun di web. Buat demo, link tweet saya, dan saya akan menambahkannya ke bagian remix komunitas di bawah ini.
Remix komunitas
Belum ada apa-apa di sini.