Navigasi fokus terarah (menggunakan tombol panah atau D-Pad) memungkinkan pengguna khusus keyboard dan input alternatif untuk menavigasi item interaktif berdasarkan tata letak visual 2D-nya. Hal ini sangat bermanfaat untuk antarmuka pengguna spasial (seperti media rail, panel dasbor, atau katalog bertingkat), tetapi harus diterapkan dengan hati-hati agar tidak mengganggu perilaku browser native seperti scroll area pandang.
Panduan ini membahas cara membuat navigasi arah 2D yang berperforma tinggi dan mudah diakses di dalam widget gabungan.
Prinsip Arsitektur Inti
Saat menerapkan fokus spasial tombol panah kustom, patuhi aturan dasar berikut:
- Batasi untuk widget gabungan: Jangan ganti tombol panah pada alur teks dokumen normal atau tata letak daftar vertikal sederhana yang memerlukan navigasi Tab berurutan default. Lihat Panduan Coding Aksesibilitas untuk mengetahui panduan alur dokumen umum.
- Pertahankan scroll batas native: Jika tidak ada elemen yang dapat difokuskan ke arah yang ditekan, jangan cegah perilaku browser default. Biarkan peristiwa menggelembung sehingga browser dapat men-scroll area pandang secara native. Mengganti tombol panah tanpa syarat akan merusak aksesibilitas (WCAG 1.4.10).
- Pastikan visibilitas dan perataan fokus: Saat fokus berubah, pastikan elemen yang baru difokuskan di-scroll ke tampilan (misalnya, menggunakan
scrollIntoView({ block: 'nearest', inline: 'nearest' })) sehingga tetap terlihat sepenuhnya. - Berkoordinasi dengan sistem tata letak: Tentukan posisi kandidat fokus dari geometri pembatas aktif (
getBoundingClientRect()) saat menangani setiap penekanan tombol. Hindari asumsi bahwa urutan DOM atau struktur petak tetap mewakili tata letak visual. Lihat Tata Letak CSS dan Desain Responsif untuk rekomendasi tata letak modern.
Pengelolaan Fokus yang Dapat Diakses
Penampung spasial harus mengekspos satu tab stop ke navigasi Tab Halaman berurutan. Di dalam widget, kelola fokus keyboard menggunakan Pola Tabindex Berpindah:
- Item aktif memiliki
tabindex="0". - Semua item tidak aktif memiliki
tabindex="-1". - Saat menavigasi, pindahkan fokus secara terprogram dengan menggeser atribut
tabindex="0"dan memanggilfocus().
Pemilihan Kandidat Visual
Dalam tata letak asimetris, digabungkan, atau petak, pemetaan berbasis indeks akan gagal. Sebagai gantinya, dapatkan kandidat dari geometri visual aktif untuk penekanan tombol saat ini:
- Kandidat: Menggunakan turunan yang dapat difokuskan dari penampung widget yang meneruskan
checkVisibility(). Elemen tersembunyi dapat memiliki rect berukuran nol di(0, 0)dan tidak boleh menjadi targetupatauleftyang tidak disengaja. - Geometri: Baca
getBoundingClientRect()untuk elemen yang difokuskan dan setiap kandidat saat menangani penekanan tombol. Jangan meng-cache persegi panjang ini secara global: tata letak dapat berubah karena berbagai alasan, dan sejumlah kecil pembacaan geometri tidak memerlukan banyak biaya saat menangani peristiwa utama yang dipicu pengguna. - Filter arah: Kandidat berada dalam arah yang ditekan hanya jika tepi dekatnya berada di atau di luar tepi jauh elemen saat ini pada sumbu tersebut, sehingga memungkinkan toleransi kecil untuk pembulatan sub-piksel. Misalnya, kandidat untuk
rightharus memilikicandidate.left >= current.right - tolerance. Jangan membandingkan pusat untuk menentukan apakah kandidat berada di arah tersebut. - Kandidat yang selaras terlebih dahulu: Di antara kandidat yang lulus filter arah, pilih kandidat yang luasnya pada sumbu tegak lurus tumpang-tindih dengan luas elemen saat ini. Pilih celah terkecil pada sumbu navigasi, dengan memutus ikatan dengan offset pusat tegak lurus terkecil. Hal ini membuat navigasi tetap berada di baris atau kolom visual yang berdekatan meskipun itemnya sangat lebar.
- Skor pengganti: Jika tidak ada kandidat yang tumpang-tindih pada sumbu tegak lurus, beri skor pada kandidat yang tersisa sebagai
gap + w * perpendicularCentreOffsetdan pilih nilai minimum. Gunakan bobotw >= 2sebagai titik awal, lalu sesuaikan untuk tata letak; bobot yang lebih besar mendukung kandidat yang lebih selaras. - Tidak ada kandidat: Tidak menampilkan apa pun. Pengendali peristiwa tidak boleh memanggil
preventDefault(), sehingga browser dapat men-scroll secara native.
Implementasi pastinya dapat bervariasi, tetapi pertahankan pengujian arah berbasis edge dan terapkan preferensi tumpang-tindih sebelum penggantian berbobot. Ini adalah inti dari algoritma; hindari menyajikan cache atau bobot tetap sebagai hal yang benar secara universal.
Integrasi Penekanan Tombol & Kerja Sama Scroll
Router peristiwa Anda harus mencegah scroll area pandang standar hanya saat transisi fokus spasial yang valid terjadi:
- Jangan mengintersepsi peristiwa saat tombol pengubah ditekan, saat
e.defaultPreventedsudah benar, atau saat target peristiwa terdalam menggunakan tombol panah itu sendiri. Gunakane.composedPath()[0], bukan hanyae.target, karenae.targetdapat ditargetkan ulang ke host bayangan. Pemeriksaan kontrol/yang dapat diedit secara praktis adalahtarget.matches(':read-write, select, input[type="range"], [role="slider"]'); root bayangan tertutup tidak dapat diperiksa. - Cakup pemroses ke penampung widget, sehingga beberapa widget spasial dapat berdampingan di satu halaman.
- Hanya bertindak saat elemen yang difokuskan adalah kandidat itu sendiri. Jika item berisi kontrol yang dapat difokuskan, jadikan kontrol tersebut sebagai kandidat juga atau biarkan saja; jangan selesaikan induk dengan
closest(). - Saat kandidat ditemukan, panggil
preventDefault(), perbaruitabindexroving, panggilfocus(), dan gunakanscrollIntoView({ block: 'nearest', inline: 'nearest' }). - Jika tidak ada kandidat yang ditemukan, tampilkan tanpa memanggil
preventDefault()agar scroll native berlanjut.
Hindari menambahkan pintasan satu karakter seperti h/j/k/l atau W/A/S/D untuk navigasi spasial. Tombol panah bukan tombol karakter, dan mempertahankan scroll native saat tidak ada kandidat akan menghindari pemblokiran gerakan area pandang yang tidak perlu.