Membuat komponen menu game 3D

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 demo.

Demo

Jika Anda lebih suka menonton video, berikut versi YouTube untuk postingan ini:

Ringkasan

Video game sering menampilkan menu yang kreatif dan tidak biasa, animasi, dan dalam ruang 3D kepada pengguna. Menu ini populer di game AR/VR baru karena menu tampak seperti mengambang di ruang angkasa. Hari ini kita akan menciptakan ulang hal-hal penting dari efek ini, tetapi dengan gaya tambahan skema warna adaptif dan akomodasi untuk pengguna yang lebih menyukai gerakan yang lebih sedikit.

HTML

Menu game adalah daftar tombol. Cara terbaik untuk merepresentasikannya 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 muncul dengan baik ke teknologi pembaca layar dan berfungsi tanpa JavaScript atau CSS.

daftar berbutir yang terlihat sangat umum dengan 
tombol reguler sebagai item.

CSS

Menata gaya daftar tombol dibagi menjadi langkah-langkah tingkat tinggi berikut:

  1. Menyiapkan properti kustom.
  2. Tata letak flexbox.
  3. Tombol kustom dengan elemen pseudo dekoratif.
  4. Menempatkan elemen ke dalam ruang 3D.

Ringkasan properti kustom

Properti kustom membantu membedakan nilai dengan memberikan nama yang bermakna menjadi nilai yang tampak acak, menghindari kode berulang, dan berbagi nilai di antara turunan.

Di bawah ini adalah kueri media yang disimpan sebagai variabel CSS, yang juga dikenal sebagai media kustom. Ini bersifat global dan akan digunakan di berbagai pemilih agar kode tetap ringkas dan dapat dibaca. Komponen menu game menggunakan preferensi gerakan, skema warna, 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 menahan nilai posisi mouse agar menu game interaktif untuk diarahkan. Penamaan properti kustom membantu keterbacaan kode karena menampilkan kasus penggunaan nilai atau nama yang cocok 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 yang cerah hingga deeppink, sedangkan tema gelap memiliki gradien kerucut gelap yang halus. 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);
  }
}
Demonstrasi latar belakang yang berubah antara preferensi warna terang dan gelap.

Mengaktifkan perspektif 3D

Agar elemen ada dalam ruang 3D halaman web, area pandang dengan perspektif harus diinisialisasi. Saya memilih 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 tata letak makro daftar tombol secara keseluruhan serta menjadi kartu mengambang 3D dan interaktif. Berikut cara untuk melakukannya.

Tata letak grup tombol

Flexbox dapat mengelola tata letak penampung. Ubah arah default flex dari baris menjadi kolom dengan flex-direction dan pastikan setiap item sesuai dengan ukuran 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 fungsi clamp() CSS untuk memastikan kartu tidak berputar melebihi rotasi yang dapat dibaca. Perhatikan bahwa nilai tengah untuk klem 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 mengganggu 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, yang memungkinkan transisi yang lancar ke perubahan rotasi. Animasi adalah animasi yang berjalan secara konstan 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 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 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. Gaya display diubah agar item tidak menampilkan ::marker. Gaya position ditetapkan ke relative sehingga elemen semu tombol berikutnya dapat memosisikannya sendiri 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;
}

Screenshot daftar yang diputar dalam ruang 3D untuk menampilkan perspektif, dan
setiap item daftar tidak lagi memiliki butir.

Menata gaya elemen <button>

Menata gaya tombol bisa menjadi pekerjaan berat karena ada banyak status dan jenis interaksi yang harus diperhitungkan. Tombol-tombol ini menjadi kompleks dengan cepat karena penyeimbangan elemen pseudo, 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;
}

Screenshot daftar tombol dalam perspektif 3D, kali ini dengan tombol
bergaya.

Elemen pseudo tombol

Batas tombol bukanlah batas tradisional, melainkan posisi mutlak elemen semu dengan batas.

Screenshot panel Elemen Devtools Chrome dengan tombol yang ditampilkan memiliki elemen
::before dan ::after.

Elemen-elemen ini sangat penting dalam menampilkan perspektif 3D yang telah ditetapkan. Salah satu elemen semu ini akan didorong menjauh dari tombol, dan salah satu elemen akan ditarik lebih dekat ke pengguna. Efeknya paling terlihat di 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 jarak sendiri pada sumbu z. transform ditetapkan 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 menyetujui gerakan, tombol akan memberi tahu browser bahwa properti transformasi harus siap diubah dan transisi ditetapkan untuk properti transform dan background-color. Perhatikan perbedaan durasi, saya merasa itu dibuat untuk 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 arahkan kursor dan fokus

Tujuan animasi interaksi adalah untuk menyebarkan lapisan yang membentuk tombol muncul datar. Selesaikan 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, CSS berlaku untuk melakukan hal berikut:

  • 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 keyboard, pembaca layar, gamepad, sentuhan, dan mouse, tetapi kita dapat menambahkan sedikit sentuhan JavaScript untuk memudahkan beberapa skenario.

Mendukung tombol panah

Tombol tab adalah cara yang tepat 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 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 mouse

Melacak mouse dan menggerakkannya untuk memiringkan menu dimaksudkan untuk meniru antarmuka video game AR dan VR, yang alih-alih mouse 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 gerakan pengguna. Selain itu, sebagai bagian dari penyiapan, simpan komponen daftar tombol ke dalam memori dengan querySelector dan simpan cache batas elemen ke 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 kita gunakan untuk memutar kartu. Fungsi berikut menggunakan posisi mouse untuk menentukan di sisi mana kotak berada 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, perhatikan pergerakan mouse, teruskan posisi ke fungsi getAngles(), dan gunakan nilai delta sebagai gaya properti kustom. saya bagi dengan 20 untuk memberikan padding pada delta dan membuatnya tidak terlalu gugup, 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 terlalu memutar kartu 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 kesalahan saat menguji menu game dalam mode penulisan dan bahasa lainnya.

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 untuk mengubah arah menu, karena elemen <a> tidak memiliki gaya !important yang disediakan browser.

Kesimpulan

Sekarang setelah Anda tahu cara saya melakukannya, bagaimana Anda‽ 🙂 Bisakah Anda menambahkan interaksi akselerometer ke menu, jadi layar ponsel akan berputar? Bisakah kita memperbaiki pengalaman {i>no motion<i}?

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.

Remix komunitas

Belum ada apa-apa di sini.