Cara menulis antarmuka pengguna (UI) PlayStation 5 di JavaScript

Demo interaktif PS5.js



Berikut adalah demo dari PS5 UI yang dibuat dengan JavaScript dan animasi CSS yang akan kami tulis dalam tutorial ini. Contoh interaktif dapat disentuh di artikel aslinya .





Letakkan proyek asterisk atau forknite ps5.js 35.9 KB di GitHub.



Saya menulis tweet tentang demo PS3 ketika saya sedang membangun versi dasar dari UI konsol PS 3 di JavaScript . Saya belum memiliki kodenya, tetapi saya berencana untuk mempostingnya. Selain itu, tutorial ini dibangun di atas pengetahuan yang diperoleh saat membuat pekerjaan pertama.



Latihan



Agar tidak mempersulit hidup kami, kami tidak akan menggunakan kerangka kerja apa pun.



Tetapi bahkan jika Anda menggunakan kerangka kerja atau pustaka, Anda masih perlu mengembangkan pola Anda sendiri untuk memecahkan masalah. Dalam tutorial UI ini, saya akan memandu Anda melalui konsep di balik pengembangan. Pendekatan ini dapat dengan mudah diadaptasi ke React, Vue, atau Angular.



Saya menggunakan file HTML template ini dengan gaya fleksibel yang sudah dibuat sebelumnya. Ini berisi semua yang Anda butuhkan dan struktur umum aplikasi untuk memulai. Ini bukan React atau Vue, tapi ini adalah konfigurasi minimum yang diperlukan untuk membuat aplikasi. Saya menggunakan blank ini setiap kali saya perlu mulai mengerjakan aplikasi atau situs web vanilla baru.



HTML dan CSS



Pada bagian ini, saya akan menjelaskan beberapa dasar dari mematikan file HTML.



Kerangka Kerja CSS DIY Sederhana



Saya bukan penggemar berat kerangka CSS dan lebih suka memulai dari awal. Namun, setelah ribuan jam pengkodean, Anda mulai melihat pola yang sering berulang. Mengapa tidak membuat beberapa kelas sederhana untuk mencakup kasus yang paling umum? Ini mencegah kita mengetik nama dan nilai properti yang sama ratusan kali.



.rel { position: relative }
.abs { position: absolute }

.top { top: 0 }
.left { left: 0 }
.right { right: 0 }
.bottom { bottom: 0 }

/* flex */
.f { display: flex; }
.v { align-items: center }
.vs { align-items: flex-start }
.ve { align-items: flex-end }
.h { justify-content: center }
.hs { justify-content: flex-start }
.he { justify-content: flex-end }
.r { flex-direction: row }
.rr { flex-direction: row-reverse }
.c { flex-direction: column }
.cr { flex-direction: column-reverse }
.s { justify-content: space-around }

.zero-padding { padding: 0 }

.o { padding: 5px }
.p { padding: 10px }
.pp { padding: 20px }
.ppp { padding: 30px }
.pppp { padding: 50px }
.ppppp { padding: 100px }

.m { margin: 5px }
.mm { margin: 10px }
.mmm { margin: 20px }
.mmmm { margin: 30px }
      
      





Kelas CSS ini berbicara sendiri.



Gaya CSS pertama kami



Sekarang setelah kita menyiapkan CSS dasar, mari tambahkan beberapa gaya untuk mengubah tampilan wadah menu yang disembunyikan dan ditampilkan. Ingatlah bahwa karena kita memiliki banyak menu dan dapat beralih di antaranya, kita perlu menentukan menu mana yang "aktif" dan mana yang "tidak aktif".



Yang saya maksud dengan banyak menu adalah bahwa setiap menu memiliki layarnya sendiri, yang ditentukan oleh elemen HTML yang terpisah. Saat beralih ke menu berikutnya, penampung sebelumnya disembunyikan dan yang baru ditampilkan. Transisi CSS juga dapat digunakan untuk membuat transisi UX yang mulus dengan mengubah opasitas, posisi, dan skala.



Semua kontainer dengan kelas .menu



default akan berada dalam status "off" (yaitu, tersembunyi). Elemen apa pun dengan kelas .menu



dan .current



akan berada dalam status "aktif" dan ditampilkan di layar.



Elemen lain, seperti tombol yang dapat dipilih di menu, menggunakan kelas itu sendiri .current



, tetapi dalam konteks hierarki CSS yang berbeda. Kami akan menjelajahi gaya CSS mereka di bagian selanjutnya dari tutorial.



#ps5 {
   width: 1065px;
   height: 600px;
   background: url('https://semicolon.dev/static/playstation_5_teaser_v2.jpg');
   background-size: cover;
}

/* default menu container - can be any UI screen */
#ps5 section.menu {
    display: none;
    opacity: 0;

    // gives us automatic transitions between opacities
    // which will create fade in/fade out effect.
    // without writing any additional JavaScript
    transition: 400ms;      
}

#ps5 section.menu.current {
    display: flex;
    opacity: 1;
}
      
      





section.menu



sekali lagi adalah wadah induk standar untuk semua lapisan menu yang kita buat. Ini bisa menjadi layar "browser game" atau layar "pengaturan". Ini tidak terlihat secara default sampai kita menerapkan classlist



kelas ke properti elemen .current



.



A section.menu.current



menunjukkan menu yang saat ini dipilih. Semua menu lainnya harus tidak terlihat dan kelas .current



tidak boleh diterapkan ke lebih dari satu menu pada saat yang bersamaan!



Html



Kerangka kerja CSS kecil buatan kami sangat menyederhanakan HTML. Inilah kerangka utamanya:



<body>
    <section id = "ps5" class = "rel">
        <section id = "system" class = "menu f v h"></section>
        <section id = "main" class = "menu f v h"></section>
        <section id = "browser" class = "menu f v h"></section>
        <section id = "settings" class = "menu f v h"></section>
    </section>
</body>
      
      





Elemen ps5



adalah wadah utama aplikasi.



Bagian utamanya flex



adalah f v h



untuk memusatkan elemen, jadi kita akan sering melihat kombinasi ini.



Juga kita akan bertemu, f r



bukan flex-direction:row;



dan f c



sebagai gantinya flex-direction:column;



.



Subbagian adalah area terpisah dari menu yang membutuhkan kelas menu



. Kita bisa beralih di antara mereka.



Dalam kode, mereka akan dihitung oleh objek yang dibekukan (kita akan melihat ini di bawah).



Mengganti latar belakang



Salah satu tugas pertama yang ingin saya tangani adalah fungsi perubahan latar belakang. Jika saya dapat menerapkannya terlebih dahulu, maka saya akan mengintegrasikannya nanti ke semua fungsi masa depan yang perlu mengubah latar belakang. Untuk ini, saya memutuskan untuk membuat dua div



.



Ketika latar belakang baru menjadi aktif, saya cukup menukar dua div



, mengganti nilai properti style.background



dengan URL gambar baru, dan menerapkan kelas ke latar belakang baru .fade-in



, menghapusnya dari yang sebelumnya.



Saya mulai dengan CSS berikut:



#background-1, #background-2 {
    position: absolute;
    top: 0;
    left: 0;
    width: inherit;
    height: inherit;
    background: transparent;
    background-position: center center;
    background-size: cover;
    pointer-events: none;
    transition: 300ms;
    z-index: 0;
    opacity: 0;
    transform: scale(0.9)
}

/* This class will be applied from Background.change() function */
.fade-in { opacity: 1 !important; transform: scale(1.0) !important; z-index: 1 }

/* set first visible background */
#background-2 { background-image: url(https://semicolon.dev/static/playstation_5_teaser_v2.jpg); }
      
      





Kemudian saya membuat fungsi statis pembantu .change



yang berasal dari kelas Background



yang menukar dua div



dan memudarkannya masuk atau keluar (fungsi tersebut mengambil satu argumen, URL dari gambar berikutnya):



class Background {constructor() {}}

Background.change = url => {

    console.log(`Changing background to ${url}`)

    let currentBackground = $(`.currentBackground`);
    let nextBackground = $(`.nextBackground`);

    // set new background to url
    nextBackground.style.backgroundImage = `url(${url})`

    // fade in and out
    currentBackground.classList.remove('fade-in')
    nextBackground.classList.add('fade-in')

    // swap background identity
    currentBackground.classList.remove('currentBackground')
    currentBackground.classList.add('nextBackground')
    nextBackground.classList.remove('nextBackground')
    nextBackground.classList.add('currentBackground')
    
}
      
      





Sekarang, setiap kali saya perlu menampilkan latar belakang baru, saya cukup memanggil fungsi ini dengan URL gambar yang akan ditampilkan:



Background.change('https://semicolon.dev/static/background-1.png')
      
      





Fade in akan dilakukan secara otomatis karena transform: 300ms



sudah diterapkan ke setiap latar belakang dan kelas .fade-in



sedang melakukan sisanya.



Cara membuat menu navigasi utama



Sekarang kerangka dasar sudah siap, kita bisa mulai membangun UI lainnya. Tapi kita juga perlu menulis kelas untuk mengelola UI. Sebut saja kelas ini PS5Menu



. Saya akan menjelaskan cara menggunakannya di bawah ini.



Layar sistem



CSS sederhana digunakan untuk membuat tombol Start . Setelah menekan tombol oleh pengguna, kita masuk ke menu utama PS5. Mari tempatkan tombol Mulai di menu pertama di layar - di menu Sistem:



<section id = "system" class = "menu f v h">
    <div id = "start" class = "f v h">Start</div>
</section>
      
      





Demikian pula, konten dari semua menu lainnya akan ditempatkan di elemen wadah induk yang sesuai.



Kami akan membahasnya nanti. Sekarang kita perlu mencari cara untuk mengatur beberapa layar menu.



Pada titik ini, kita perlu belajar tentang konsep mengantre beberapa menu. PS5 memiliki beberapa lapisan UI navigasi yang berbeda. Misalnya, saat Anda memilih Pengaturan, menu baru yang sama sekali berbeda terbuka, dan kontrol keyboard dibawa ke menu baru ini.



Kita membutuhkan objek untuk melacak semua menu ini yang terus-menerus dibuka, ditutup, dan kemudian diganti dengan menu baru atau sebelumnya.



Anda dapat menggunakan metode bawaan push



Array objek dalam JavaScript untuk menambahkan menu baru ke antrian. Dan ketika kita perlu kembali, kita dapat memanggil metode pop



array untuk kembali ke menu sebelumnya.



Kami mencantumkan menu berdasarkan atribut id



elemen:



const MENU = Object.freeze({
    system: `system`,
      main: `main`,
   browser: `browser`,
  settings: `settings`,

/* add more if needed*/

});
      
      





Saya menggunakan Object.freeze()



agar tidak ada properti yang berubah setelah disetel. Beberapa jenis objek sebaiknya dibekukan. Ini adalah objek yang pasti tidak boleh berubah selama masa pakai aplikasi.



Di sini, setiap nilai adalah nama properti dalam format string. Dengan cara ini kita dapat menautkan ke item menu dengan MENU.system



atau MENU.settings



. Tidak ada apa pun selain estetika sintaksis dalam pendekatan ini, dan ini juga merupakan cara sederhana untuk menghindari penyimpanan semua objek menu "dalam satu keranjang".



Kelas PS5Menu



Pertama, saya membuat kelas PS5Menu



. Konstruktornya menggunakan properti this.queue



type Array



.



// menu queue object for layered PS5 navigation
class PS5Menu {

    constructor() {
        this.queue = []
    }

    set push(elementId) {
        // hide previous menu on the queue by removing "current" class
        this.queue.length > 0 && this.queue[this.queue.length - 1].classList.remove(`current`)

        // get menu container
        const menu = $(`#${elementId}`) 

        // make the new menu appear by applying "current" class
        !menu.classList.contains(`current`) && menu.classList.add(`current`)
        
        // push this element onto the menu queue
        this.queue.push( menu ) 

        console.log(`Pushed #${elementId} onto the menu queue`)
    }

    pop() {
        // remove current menu from queue
        const element = this.queue.pop()

        console.log(`Removed #${element.getAttribute('id')} from the menu queue`)
    }
}
      
      





Bagaimana cara menggunakan kelas PS5Menu?



Kelas ini memiliki dua metode, penyetel dan fungsi statis . Mereka akan melakukan hal yang hampir sama seperti metode array dan melakukannya dengan array kita . Misalnya, untuk membuat instance menu kelas dan menambah atau menghapusnya dari menu tumpukan, kita dapat memanggil metode dan langsung dari instance kelas. push(argument)



pop()



.push()



.pop



this.queue





push



pop







// instantiate the menu object from class
const menu = new PS5Menu()

// add menu to the stack
menu.push = `system`

// remove the last menu that was pushed onto the stack from it
menu.pop()
      
      





Fungsi penyetel kelas seperti ini set push()



tidak bisa dipanggil dengan ()



. Mereka menetapkan nilai menggunakan operator penugasan =



. Fungsi penyetel kelas set push()



akan dijalankan dengan parameter ini.



Mari gabungkan semua yang telah kita lakukan:



/* Your DOM just loaded */
window.addEventListener('DOMContentLoaded', event => {      

    // Instantiate the queable menu
    const menu = new PS5Menu()

    // Push system menu onto the menu
    menu.push = `system`

    // Attach click event to Start button
    menu.queue[0].addEventListener(`click`, event => {

        console.log(`Start button pressed!`)

        // begin the ps5 demo!
        menu.push = `main`
    });

});
      
      





Di sini kita telah membuat sebuah instance dari kelas PS5Menu



dan menyimpan instance objeknya dalam sebuah variabel menu



.



Kemudian, kami mengantri beberapa menu dengan menu pertama dengan id #system



.



Selanjutnya, kami melampirkan acara ke tombol Mulaiclick



. Ketika kita mengklik tombol ini, kita membuat menu utama (dengan id



, sama dengan main



) menu kita saat ini. Dalam hal ini, menu sistem akan disembunyikan (menu saat ini ada dalam antrian menu) dan penampung akan ditampilkan #menu



.



Perhatikan bahwa karena kelas wadah menu kita .menu.current



memiliki properti transform: 400ms;



, lalu dengan penambahan atau penghapusan sederhana kelas .current



dari sebuah elemen, properti yang baru ditambahkan atau dihapus akan beranimasi dalam 0,4 milidetik.



Sekarang Anda perlu memikirkan tentang cara membuat konten untuk menu utama.



Perhatikan bahwa langkah ini dilakukan dalam acara DOM "Content Loaded" ( DOMContentLoaded



). Ini harus menjadi titik masuk untuk aplikasi UI apa pun. Titik masuk kedua adalah sebuah acara window.onload



, tetapi dalam demo ini kami tidak membutuhkannya. Itu menunggu media (gambar, dll.) Untuk menyelesaikan pengunduhan, yang bisa terjadi lebih lama dari elemen DOM tersedia.



Layar splash



Awalnya, UI utama adalah rangkaian dari beberapa elemen. Seluruh baris muncul dari tepi kanan layar. Saat pertama kali muncul, itu dianimasikan dengan menyeret ke kiri.



Saya telah menyematkan elemen-elemen ini ke dalam wadah #main



seperti ini:



<section id = "main" class = "menu f v h">
    <section id = "tab" class = "f">
        <div class = "on">Games</div>
        <div>Media</div>
    </section>
    <section id = "primary" class = "f">
        <div class = "sel t"></div>
        <div class = "sel b current"></div>
        <div class = "sel a"></div>
        <div class = "sel s"></div>
        <div class = "sel d"></div>
        <div class = "sel e"></div>
        <div class = "sel"></div>
        <div class = "sel"></div>
        <div class = "sel"></div>
        <div class = "sel"></div>
        <div class = "sel"></div>
    </section>
</section>
      
      





Menu PS5 pertama ditempatkan di dalam wadah induk, dengan gaya sebagai berikut:



#primary {
    position: absolute;
    top: 72px;
    left: 1200px;
    width: 1000px;
    height: 64px;
    opacity: 0;

    /* animate at the rate of 0.4s */
    transition: 400ms;
}

#primary.hidden {
    left: 1200px;
}
      
      





Secara default, dalam keadaan tersembunyi #primary



, ini sengaja tidak ditampilkan; itu dipindahkan cukup jauh ke kanan (sebesar 1200px).



Kami harus melalui trial and error dan menggunakan intuisi kami. Sepertinya 1200px cocok. Penampung ini juga diwarisi opacity:0



dari kelas .menu



.



Jadi saat #primary



muncul untuk pertama kali, ia menggeser dan meningkatkan kecerahannya pada saat yang bersamaan.



Di sini sekali lagi nilai transform:400ms;



(ekuivalen 0.4s



) digunakan, karena sebagian besar mikroanimasi terlihat bagus 0.4s



. Nilai 0.3s



juga bekerja dengan baik, tetapi mungkin terlalu cepat dan 0.5s



terlalu lambat.



Menggunakan transisi CSS untuk mengontrol animasi UI



Alih-alih memanipulasi gaya CSS secara manual setiap kali kita perlu mengubah gaya atau posisi blok UI, kita cukup menetapkan dan menghapus kelas:



// get element:
const element = $(`#primary`)

// check if element already contains a CSS class:
element.style.classList.contains("menu")

// add a new class to element's class list:
element.style.classList.add("menu")

// remove a class from element's class list:
element.style.classList.remove("menu")
      
      





Ini adalah strategi penting yang akan menghemat banyak waktu dan menjaga kode Anda tetap bersih dalam proyek vanilla apa pun. Alih-alih mengubah properti, style.left



kami hanya akan menghapus kelas .hidden



dari elemen #primary



. Sejak itu transform:400ms;



, animasi akan diputar secara otomatis.



Kami akan menggunakan taktik ini untuk mengubah hampir setiap status elemen UI.



Animasi Slide-Out Sekunder



Saat bekerja dengan desain UX, ada berbagai jenis animasi. Beberapa animasi dipicu saat beralih ke menu baru. Mereka biasanya mulai setelah beberapa saat, tak lama setelah beralih ke layar baru.



Ada juga animasi hover yang aktif saat mouse atau pengontrol memilih item baru yang berdekatan di menu navigasi saat ini.



Perhatian terhadap detail itu penting, terutama saat Anda ingin menciptakan produk yang berkualitas.



Menggunakan fungsi setTimeout untuk mengontrol status animasi



Animasi sekunder kecil diputar saat item ditarik keluar . Untuk mensimulasikan efek ganda ini, fungsi JavaScript digunakan setTimeout



segera setelah pohon DOM dimuat sepenuhnya.



Karena ini adalah layar menu pertama yang muncul segera setelah mengklik tombol Start , kita sekarang perlu memperbarui event click



tombol Start di event DOMContentLoaded tepat setelahnya menu.push = `main`



.



Kode berikut akan duduk di bagian bawah fungsi acara yang sudah ada DOMContentLoaded



(lihat contoh kode sumber yang ditunjukkan di atas):



/* Your DOM just loaded */
window.addEventListener('DOMContentLoaded', event => {      

    /* Initial setup code goes here...see previous source code example */

    // Attach click event to Start button
    menu.queue[0].addEventListener(`click`, event => {

        console.log(`Start button pressed!`)

        // begin the ps5 demo!
        menu.push = `main`

        // new code: animate the main UI screen for the first time
        // animate #primary UI block within #main container
        primary.classList.remove(`hidden`)
        primary.classList.add(`current`)

        // animate items up
        let T1 = setTimeout(nothing => {
          
            primary.classList.add('up');

            def.classList.add('current');

            // destroy this timer
            clearInterval(T1)
            T1 = null;

        }, 500)
    });    

});
      
      





Apa hasilnya



Semua kode yang kami tulis menghasilkan animasi awal ini:





Buat item yang dapat dipilih



Kami telah membuat CSS untuk elemen yang dapat dipilih (kelas .sel



).



Tapi masih terlihat rustic, tidak semulus antarmuka PS5.



Di bagian selanjutnya, kita akan melihat kemungkinan untuk membuat antarmuka yang lebih bagus. Kami akan meningkatkan UI menjadi tampilan dan nuansa profesional dari sistem navigasi PlayStation 5.



Animasi standar dari elemen "yang dipilih" atau "saat ini"



Tiga jenis animasi untuk item yang saat ini dipilih



Di UI konsol PS5, item yang saat ini dipilih memiliki tiga efek visual. Garis besar yang berputar - "halo", titik cahaya acak yang bergerak di latar belakang, dan terakhir, "gelombang cahaya" - efek yang terlihat seperti gelombang yang bergerak ke arah tombol arah yang ditekan pada pengontrol.



Di bagian ini, kita akan belajar cara membuat efek garis besar tombol PS5 klasik dengan titik cahaya di latar belakang dan gelombang cahaya. Di bawah ini adalah analisis dari setiap jenis animasi dan kelas CSS yang kita butuhkan untuk semua jenis ini:



Halo animasi dengan gradien



Efek ini menambahkan bingkai animasi yang berputar di sekitar item yang dipilih.



Di CSS, ini dapat disimulasikan dengan memutar gradien meruncing.



Berikut garis besar CSS umum untuk elemen yang dapat dipilih:



.sel {
    position: relative;
    width: 64px;
    height: 64px;
    margin: 5px;
    border: 2px solid #1f1f1f;
    border-radius: 8px;
    cursor: pointer;
    transition: 400ms;
    transform-style: preserve-3d;
    z-index: 3;
}

.sel.current {
    width: 100px;
    height: 100px;    
}

.sel .under {
    content:'';
    position: absolute;
    width: calc(100% + 8px);
    height: calc(100% + 8px);
    margin: -4px -4px;
    background: #1f1f1f;
    transform: translateZ(-2px);
    border-radius: 8px;
    z-index: 1;
}

.sel .lightwave-container {
    position: relative;
    width: 100%;
    height: 100%;
    transition: 400ms;
    background: black;
    transform: translateZ(-1px);
    z-index: 2;
    overflow: hidden;
}

.sel .lightwave {
    position: absolute;
    top: 0;
    right: 0;
    width: 500%;
    height: 500%;    
    background: radial-gradient(circle at 10% 10%, rgba(72,72,72,1) 0%, rgba(0,0,0,1) 100%);
    filter: blur(30px);
    transform: translateZ(-1px);
    z-index: 2;
    overflow: hidden;
}
      
      





Saya mencoba menggunakan pseudo-elemen ::after



dan ::before



, tetapi saya tidak dapat mencapai hasil yang saya inginkan dengan cara yang sederhana, dan dukungan mereka oleh browser dipertanyakan; selain itu, JavaScript tidak memiliki cara asli untuk mengakses elemen semu.





Sebagai gantinya, saya memutuskan untuk membuat elemen baru .under



dan mengurangi posisi Z-nya dengan -1 menggunakan transform: translateZ(-1px)



; jadi, kami menjauhkannya dari kamera, membiarkan induknya muncul di atasnya.



Anda mungkin juga perlu menambahkan .sel



properti ke elemen induk yang diidentifikasi oleh elemen transform-style: preserve-3d;



untuk mengaktifkan urutan-z dalam ruang 3D elemen.



Idealnya, kami ingin memberi .under



induk pada lapisan ke elemen dan membuat titik terang dengan elemen tombol yang sebenarnya di dalamnya. Tetapi trik ini memiliki translateZ



prioritas yang lebih tinggi, dan begitulah cara saya mulai membangun UI. Ini dapat dikerjakan ulang, tetapi pada tahap ini tidak perlu.



HTML cukup sederhana. Yang penting di sini adalah kita sekarang memiliki elemen baru .under



. Ini adalah elemen di mana gradien kerucut berputar akan dirender untuk membuat batas bercahaya halus.



.lightwave-container



akan membantu kami menerapkan efek cahaya bergerak dengan overflow: hidden



. .lightwave



- ini adalah elemen di mana efek akan diberikan, ini adalah div yang lebih besar yang melampaui batas tombol dan berisi gradien radial offset.



<div id = "o0" data-id = "0" class = "sel b">
    <div class = "under"></div>
    <div class = "lightwave-container">
        <div class = "lightwave"></div>
    </div>
</div>
      
      





Mulai awal Maret 2021, animasi CSS tidak mendukung rotasi latar belakang gradien.



Untuk mengatasi masalah ini, saya menggunakan fungsi JavaScript bawaan window.requestAnimationFrame



. Ini dengan lancar menganimasikan properti latar belakang sesuai dengan kecepatan bingkai monitor, yang biasanya 60FPS.



// Continuously rotate currently selected item's gradient border
let rotate = () => {

    let currentlySelectedItem = $(`.sel.current .under`)
    let lightwave = $(`.sel.current .lightwave`)

    if (currentlySelectedItem) {

        let deg = parseInt(selectedGradientDegree);
        let colors = `#aaaaaa, black, #aaaaaa, black, #aaaaaa`;

        // dynamically construct the css style property
        let val = `conic-gradient(from ${deg}deg at 50% 50%, ${colors})`;

        // rotate the border
        currentlySelectedItem.style.background = val

        // rotate lightwave
        lightwave.style.transform = `rotate(${selectedGradientDegree}deg)`;

        // rotate the angle
        selectedGradientDegree += 0.8
    }
    window.requestAnimationFrame(rotate)
}
window.requestAnimationFrame(rotate)
      
      





Fungsi ini bertanggung jawab untuk menganimasikan batas yang berputar dan elemen gelombang cahaya yang lebih besar.



Paradigma Pendengar Acara



Karena kita tidak menggunakan React atau framework lain, kita perlu menangani sendiri event listenernya. Setiap kali kita mengganti menu, kita perlu melepaskan semua peristiwa mouse dari semua item di dalam wadah induk dari menu sebelumnya, dan melampirkan pendengar peristiwa mouse ke semua item interaktif di dalam wadah induk dari menu baru yang dipilih.



Setiap layar itu unik. Cara termudah adalah dengan membuat kode acara untuk setiap layar. Ini bukan peretasan, tetapi hanya kode khusus untuk setiap sistem navigasi unik. Untuk beberapa hal, tidak ada solusi yang mudah.



Dua fungsi berikutnya akan mengaktifkan dan menonaktifkan acara dari layar yang berbeda.



Lihat kode sumber PS5.js lengkapuntuk memahami bagaimana semuanya bekerja secara umum.



function AttachEventsFor(parentElementId) {

    switch (parentElementId) {
        case "system":

          break;
        case "main":

          break;
        case "browser":

          break;
        case "settings":

          break;
    }
}

function RemoveEventsFrom(parentElementId) {

    switch (parentElementId) {
        case "system":

          break;
        case "main":

          break;
        case "browser":

          break;
        case "settings":

          break;
    }
}
      
      





Ini memastikan bahwa kita tidak pernah mendengarkan lebih banyak kejadian mouse daripada yang kita miliki sehingga kode UX berjalan secara optimal untuk setiap layar menu individu.



Menavigasi dengan keyboard



Kontrol keyboard jarang digunakan dalam aplikasi web dan situs web. Jadi saya membuat pustaka keyboard vanilla JS yang mengenali tombol dasar dan memungkinkan Anda untuk menghubungkan acara penekanan tombol.



Kita perlu mencegat kunci berikut:



  • Enter atau Spasi - Memilih item yang saat ini dipilih.
  • Kiri , Kanan , Atas , Bawah - navigasi melalui menu yang saat ini dipilih.
  • Escape - Membatalkan menu antrian saat ini dan kembali ke menu sebelumnya.


Anda dapat mengikat semua kunci dasar ke variabel sebagai berikut:



// Map variables representing keys to ASCII codes
const [ A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z ] = Array.from({ length: 26 }, (v, i) => 65 + i);

const Delete = 46;
const Shift = 16;
const Ctrl = 17;
const Alt = 18;

const Left = 37;
const Right = 39;
const Up = 38;
const Down = 40;

const Enter = 13;
const Return = 13;
const Space = 32;
const Escape = 27;
      
      





Lalu buat penangan peristiwa keyboard:



function keyboard_events_main_menu(e) {

    let key = e.which || e.keyCode;

    if (key == Left) {
        if (menu.x > 0) menu.x--
    }

    if (key == Right) {
        if (menu.x < 3) menu.x++
    }

    if (key == Up) {
        if (menu.y > 0) menu.y--
    }

    if (key == Down) {
        if (menu.y < 3) menu.y++
    }

}
      
      





Dan hubungkan ke objek dokumen:



document.body.addEventListener("keydown", keyboard_events_main_menu);
      
      





Sound API



Masih mengerjakannya ...



Sementara itu, Anda dapat mengunduh di sini pustaka API suara sederhana di vanilla JS.



All Articles