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. Coba demonya.
Jika Anda lebih suka video, berikut versi YouTube postingan ini:
Ringkasan
Video game sering kali menyajikan menu yang kreatif dan tidak biasa, animasi dan dalam ruang 3D kepada pengguna. Hal ini populer di game AR/VR baru karena membuat menu tampak mengambang di luar angkasa. Hari ini kita akan membuat ulang dasar-dasar efek ini, tetapi dengan gaya tambahan skema warna adaptif dan akomodasi bagi pengguna yang lebih memilih gerakan yang dikurangi.
HTML
Menu game adalah daftar tombol. Cara terbaik untuk mewakili 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 tampil dengan baik di teknologi pembaca layar dan berfungsi tanpa JavaScript atau CSS.
CSS
Menata gaya daftar tombol dipecah menjadi langkah-langkah tingkat tinggi berikut:
- Menyiapkan properti kustom.
- Tata letak {i>flexbox<i}.
- Tombol khusus dengan elemen pseudo yang dekoratif.
- Menempatkan elemen ke dalam ruang 3D.
Ringkasan properti khusus
Properti kustom membantu membedakan nilai dengan memberikan nama yang bermakna ke nilai yang terlihat acak, menghindari kode berulang, dan berbagi nilai di antara turunan.
Berikut adalah kueri media yang disimpan sebagai variabel CSS, yang juga dikenal sebagai media kustom. Atribut ini bersifat global dan akan digunakan di berbagai pemilih agar kode tetap ringkas dan dapat dibaca. Komponen menu game menggunakan preferensi gerakan, skema warna sistem, dan kemampuan rentang warna layar.
@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 menahan nilai posisi mouse agar menu game menjadi interaktif untuk mengarahkan kursor. Penamaan properti khusus membantu keterbacaan kode karena akan mengungkapkan kasus penggunaan untuk nilai atau nama yang dikenal 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 gradien kerucut cyan
hingga deeppink
yang cerah, sedangkan tema gelap memiliki gradien kerucut halus gelap. Untuk mengetahui lebih lanjut apa yang 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 di ruang 3D halaman web, area tampilan dengan perspektif perlu diinisialisasi. Saya memilih untuk menempatkan perspektif pada elemen body
dan menggunakan unit area tampilan untuk membuat gaya yang saya sukai.
body {
perspective: 40vw;
}
Inilah jenis perspektif dampak yang dapat muncul.
Menata gaya daftar tombol <ul>
Elemen ini bertanggung jawab atas keseluruhan tata letak makro daftar tombol serta menjadi kartu mengambang 3D yang interaktif. Berikut adalah cara untuk melakukannya.
Tata letak grup tombol
Flexbox dapat mengelola tata letak container. Ubah arah default flex
dari baris ke kolom dengan flex-direction
dan pastikan setiap item sesuai dengan ukuran
kontennya 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 container sebagai konteks ruang 3D dan siapkan fungsi clamp()
CSS untuk memastikan kartu tidak berputar di luar rotasi yang dapat dibaca. Perhatikan
bahwa nilai tengah untuk clamp adalah properti khusus, nilai --x
dan --y
ini akan ditetapkan dari JavaScript setelah 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 cocok bagi pengguna yang berkunjung, tambahkan petunjuk ke browser bahwa
transformasi item ini akan terus berubah dengan
will-change
.
Selain itu, aktifkan interpolasi dengan menetapkan transition
pada transformasi. Transisi
ini akan terjadi saat mouse berinteraksi dengan kartu, sehingga memungkinkan
transisi yang lancar ke perubahan rotasi. Animasi adalah animasi yang berjalan secara konstan
yang menunjukkan ruang 3D tempat kartu berada, sekalipun 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 menetapkan keyframe tengah pada 50%
karena browser akan menetapkan 0%
dan 100%
secara default ke gaya default elemen. Ini
adalah singkatan untuk animasi yang bergantian, yang harus dimulai dan diakhiri pada posisi
yang sama. Ini adalah cara yang bagus untuk mengartikulasikan animasi bolak-balik yang tak terbatas.
@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
disetel ke relative
sehingga elemen pseudo tombol berikutnya dapat memosisikan
dirinya di 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>
Menata gaya tombol bisa jadi sulit, ada banyak status dan jenis interaksi yang harus diperhitungkan. Tombol-tombol ini menjadi kompleks dengan cepat karena menyeimbangkan elemen pseudo, animasi, dan interaksi.
Gaya <button>
awal
Di bawah ini 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 pseudo tombol
Batas tombol bukanlah batas tradisional, melainkan merupakan elemen semu dengan batas yang bersifat mutlak.
Elemen-elemen ini sangat penting dalam menunjukkan perspektif 3D yang telah dibentuk. Salah satu elemen pseudo ini akan didorong dari tombol, dan satu elemen akan ditarik lebih dekat ke 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
disetel ke preserve-3d
sehingga turunan dapat menempatkan
diri sendiri pada sumbu z
. transform
disetel ke properti kustom
--distance
, yang akan ditingkatkan saat 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 mengizinkan gerakan, tombol memberi petunjuk kepada browser bahwa
properti transformasi harus siap untuk diubah dan transisi ditetapkan untuk
properti transform
dan background-color
. Perhatikan perbedaan durasinya, saya merasa hal itu dibuat untuk efek bertahap yang 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 }
}
}
Mengarahkan kursor dan memfokuskan gaya interaksi
Tujuan animasi interaksi adalah untuk menyebarkan lapisan yang membentuk
tombol muncul datar. Lakukan hal ini dengan menetapkan variabel --distance
,
awalnya ke 1px
. Pemilih yang ditampilkan dalam contoh kode berikut memeriksa apakah
tombol diarahkan atau difokuskan oleh perangkat yang seharusnya melihat
indikator fokus, dan tidak diaktifkan. Jika demikian, terapkan CSS untuk melakukan hal berikut:
- Terapkan warna latar belakang pengarahan kursor.
- Naikkan jarak .
- Tambahkan efek mudah memantul.
- Transisi elemen pseudo 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 menarik.
Peningkatan kecil dengan JavaScript
Antarmuka dapat digunakan dari keyboard, pembaca layar, gamepad, sentuhan, dan mouse, tetapi kita dapat menambahkan beberapa sentuhan ringan pada JavaScript untuk memudahkan beberapa skenario.
Tombol panah pendukung
Tombol {i>tab<i} adalah cara yang baik untuk menavigasi menu, tetapi saya mengharapkan
tombol arah atau joystick memindahkan fokus pada gamepad. Library roving-ux yang sering digunakan untuk antarmuka Tantangan GUI akan menangani tombol panah untuk kita. Kode di bawah ini memberi tahu
library untuk menangkap 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 tikus
Melacak mouse dan memiringkan menu dimaksudkan untuk meniru antarmuka video game AR dan VR, di mana Anda dapat memiliki pointer virtual, bukan mouse. Akan menyenangkan jika elemen sangat menyadari pointer.
Karena ini adalah fitur tambahan kecil, kita 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 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 serta menampilkan nilai yang dapat digunakan untuk memutar kartu. Fungsi berikut menggunakan posisi mouse
untuk menentukan sisi kotak mana dan seberapa banyak. 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, lihat pergerakan mouse, teruskan posisi ke fungsi getAngles()
,
dan gunakan nilai delta sebagai gaya properti kustom. saya membaginya dengan 20 untuk meratakan
delta dan membuatnya tidak bergerak, mungkin ada cara yang lebih baik. Jika Anda
ingat dari awal, kita menempatkan properti --x
dan --y
di tengah
fungsi clamp()
, hal ini mencegah posisi mouse memutar
kartu secara berlebihan 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 rute
Ada satu kesalahan saat menguji menu game dalam mode penulisan dan bahasa lainnya.
Elemen <button>
memiliki gaya !important
untuk writing-mode
di stylesheet
agen pengguna. Ini berarti 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
Setelah Anda tahu cara saya melakukannya, bagaimana Anda‽ 🙂 Bisakah Anda menambahkan interaksi akselerometer ke menu, sehingga membuat ubin ponsel akan memutar menu? Bisakah kita memperbaiki pengalaman tanpa {i>motion<i}?
Mari lakukan diversifikasi pendekatan dan pelajari semua cara untuk membangun di web. Buat demo, link tweet me, dan saya akan menambahkannya ke bagian remix komunitas di bawah.
Remix komunitas
Belum ada apa-apa di sini.