Ringkasan dasar tentang cara membuat menu game 3D yang responsif, adaptif, dan mudah diakses.
Dalam postingan ini, saya ingin berbagi pemikiran tentang cara membuat komponen menu game 3D. Coba demo.
Jika Anda lebih suka menonton video, berikut versi YouTube dari postingan ini:
Ringkasan
Video game sering kali menyajikan menu yang kreatif dan tidak biasa kepada pengguna, yang dianimasikan dan dalam ruang 3D. Menu yang tampak mengambang di ruang angkasa populer di game AR/VR baru. Hari ini kita akan membuat ulang esensi efek ini, tetapi dengan sentuhan tambahan skema warna adaptif dan akomodasi untuk pengguna yang lebih menyukai gerakan yang dikurangi.
HTML
Menu game adalah daftar tombol. Cara terbaik untuk merepresentasikan hal ini dalam HTML adalah sebagai berikut:
<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 diumumkan dengan baik oleh teknologi pembaca layar dan berfungsi tanpa JavaScript atau CSS.

CSS
Penataan gaya daftar tombol dipecah menjadi langkah-langkah tingkat tinggi berikut:
- Menyiapkan properti kustom.
- Tata letak flexbox.
- Tombol kustom dengan elemen semu dekoratif.
- Menempatkan elemen ke dalam ruang 3D.
Ringkasan properti kustom
Properti kustom membantu membedakan nilai dengan memberikan nama yang bermakna pada nilai yang terlihat acak, menghindari kode yang berulang, dan membagikan nilai di antara turunan.
Di bawah ini adalah kueri media yang disimpan sebagai variabel CSS, yang juga dikenal sebagai media kustom. Variabel ini bersifat global dan akan digunakan di berbagai pemilih untuk menjaga kode tetap ringkas dan mudah dibaca. Komponen menu game menggunakan preferensi gerakan, skema warna sistem, dan kemampuan rentang warna tampilan.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);
Properti kustom berikut mengelola skema warna dan menyimpan nilai posisi mouse untuk membuat menu game interaktif saat kursor diarahkan. Penamaan properti kustom membantu keterbacaan kode karena mengungkapkan kasus penggunaan untuk nilai atau nama yang mudah diingat 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 untuk tema terang dan gelap
Tema terang memiliki gradien kerucut cyan hingga deeppink yang cerah, sedangkan tema gelap memiliki gradien kerucut gelap yang halus. Untuk melihat lebih lanjut apa yang dapat dilakukan dengan gradien konis, 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 di ruang 3D halaman web, area tampilan dengan
perspektif
harus diinisialisasi. Saya memilih untuk menempatkan perspektif pada elemen body
dan menggunakan unit viewport untuk membuat gaya yang saya sukai.
body {
perspective: 40vw;
}
Inilah jenis dampak yang dapat dimiliki perspektif.
Menata gaya daftar tombol <ul>
Elemen ini bertanggung jawab atas tata letak makro daftar tombol secara keseluruhan serta menjadi kartu mengambang 3D yang interaktif. Berikut cara melakukannya.
Tata letak grup tombol
Flexbox dapat mengelola tata letak penampung. Ubah arah default flex
dari baris ke kolom dengan flex-direction dan pastikan setiap item berukuran
sesuai kontennya dengan mengubah dari stretch ke 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 fungsi CSS clamp()
untuk memastikan kartu tidak berputar di luar rotasi yang dapat dibaca. Perhatikan
bahwa nilai tengah untuk clamp adalah properti kustom, nilai --x dan --y
ini akan ditetapkan dari JavaScript saat interaksi
mouse 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 gerakan tidak masalah bagi pengguna yang berkunjung, tambahkan petunjuk ke browser bahwa transformasi item ini akan terus berubah dengan will-change.
Selain itu, aktifkan interpolasi dengan menyetel transition pada transformasi. Transisi ini akan terjadi saat kursor berinteraksi dengan kartu, sehingga memungkinkan transisi yang lancar ke perubahan rotasi. Animasi adalah animasi yang berjalan terus-menerus yang menunjukkan ruang 3D tempat kartu berada, meskipun mouse 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 menyetel keyframe tengah pada 50% karena browser akan menetapkan 0% dan 100% ke gaya default elemen. Ini
adalah singkatan untuk animasi yang bergantian, yang perlu dimulai dan diakhiri pada posisi yang sama. Cara ini efektif untuk mengartikulasikan animasi bolak-balik tanpa batas.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
Menata gaya elemen <li>
Setiap item daftar (<li>) berisi tombol dan elemen batasnya. Gaya
display diubah sehingga item tidak menampilkan
::marker. Gaya position
ditetapkan ke relative sehingga elemen semu tombol yang akan datang dapat memosisikan
diri dalam area penuh yang digunakan 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>
Memberi gaya pada tombol bisa menjadi pekerjaan yang sulit, ada banyak status dan jenis interaksi yang harus diperhitungkan. Tombol ini menjadi rumit dengan cepat karena menyeimbangkan pseudo-elemen, animasi, dan interaksi.
Gaya <button> awal
Berikut adalah gaya dasar yang akan mendukung status lainnya.
.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 semu tombol
Batas tombol bukan batas tradisional, melainkan elemen semu posisi mutlak dengan batas.

Elemen ini sangat penting dalam menampilkan perspektif 3D yang telah ditetapkan. Salah satu elemen semu ini akan didorong menjauhi tombol, dan salah satunya akan ditarik mendekati pengguna. Efeknya paling terlihat pada 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 ditetapkan ke preserve-3d sehingga turunan dapat
menempatkan diri di sumbu z. transform disetel ke properti kustom --distance, yang akan bertambah saat kursor diarahkan 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 tidak keberatan dengan gerakan, petunjuk tombol memberi tahu browser bahwa properti transform harus siap untuk diubah dan transisi ditetapkan untuk properti transform dan background-color. Perhatikan perbedaan durasi, saya merasa ini menghasilkan efek bertahap 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 mengarahkan kursor dan fokus
Tujuan animasi interaksi adalah menyebarkan lapisan yang membentuk tombol yang tampak datar. Lakukan ini dengan menyetel variabel --distance, awalnya ke 1px. Pemilih yang ditampilkan dalam contoh kode berikut memeriksa
apakah tombol sedang di-hover atau difokuskan oleh perangkat yang seharusnya melihat
indikator fokus, dan tidak diaktifkan. Jika demikian, CSS akan diterapkan untuk melakukan
hal berikut:
- Terapkan warna latar belakang saat kursor diarahkan.
- Tingkatkan jarak .
- Tambahkan efek kemudahan pantulan.
- Menyusun transisi elemen semu.
.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 menunjukkan efek dengan cara yang halus dan bagus.
Peningkatan kecil dengan JavaScript
Antarmuka dapat digunakan dari keyboard, pembaca layar, gamepad, sentuhan, dan mouse, tetapi kita dapat menambahkan beberapa sentuhan ringan JavaScript untuk mempermudah beberapa skenario.
Mendukung tombol panah
Tombol tab adalah cara yang baik untuk menavigasi menu, tetapi saya berharap tombol arah atau joystick dapat memindahkan fokus pada gamepad. Library
roving-ux yang sering digunakan untuk antarmuka
Tantangan GUI akan menangani tombol panah untuk kita. Kode di bawah memberi tahu
pustaka 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 memiringkan menu dimaksudkan untuk meniru antarmuka game video AR dan VR, yang menggunakan pointer virtual, bukan mouse. Elemen dapat menjadi menyenangkan jika sangat menyadari keberadaan pointer.
Karena ini adalah fitur tambahan kecil, kami akan menempatkan interaksi di balik kueri preferensi gerakan pengguna. Selain itu, sebagai bagian dari penyiapan, simpan komponen daftar tombol ke dalam memori dengan querySelector dan simpan batas elemen ke dalam menuRect. Gunakan batas ini untuk menentukan offset rotasi yang diterapkan ke 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 serta menampilkan
nilai yang dapat kita gunakan untuk memutar kartu. Fungsi berikut menggunakan posisi mouse untuk menentukan sisi kotak tempat kursor berada dan seberapa jauh. 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, amati pergerakan mouse, teruskan posisi ke fungsi getAngles()
dan gunakan nilai delta sebagai gaya properti kustom. Saya membagi dengan 20 untuk mengisi
delta dan membuatnya tidak terlalu bergetar, mungkin ada cara yang lebih baik untuk melakukannya. Jika Anda
ingat dari awal, kita menempatkan properti --x dan --y di tengah
fungsi clamp(), sehingga posisi mouse tidak akan memutar kartu secara berlebihan
hingga tidak dapat dibaca.
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 petunjuk arah
Ada satu hal yang perlu diperhatikan saat menguji menu game dalam mode penulisan dan bahasa lain.
Elemen <button> memiliki gaya !important untuk writing-mode di stylesheet agen pengguna. Artinya, HTML menu game perlu diubah untuk mengakomodasi desain yang diinginkan. Mengubah daftar tombol menjadi daftar link memungkinkan properti
logis mengubah arah menu, karena elemen <a> tidak memiliki gaya !important yang
disediakan browser.
Kesimpulan
Sekarang setelah Anda tahu cara saya melakukannya, bagaimana Anda‽ 🙂 Dapatkah Anda menambahkan interaksi akselerometer ke menu, sehingga memiringkan ponsel akan memutar menu? Bisakah kita meningkatkan kualitas pengalaman tanpa gerakan?
Mari kita diversifikasi pendekatan kita dan pelajari semua cara untuk membangun di web. Buat demo, tweet linknya kepada saya, dan saya akan menambahkannya ke bagian remix komunitas di bawah.
Remix komunitas
Belum ada apa-apa di sini.