Membuat komponen sidenav

Ringkasan dasar tentang cara membuat sidenav slide keluar responsif

Dalam postingan ini, saya ingin berbagi cara membuat prototipe komponen Sidenav untuk web yang responsif, memiliki status, mendukung navigasi keyboard, berfungsi dengan dan tanpa JavaScript, serta berfungsi di seluruh browser. Coba demo.

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

Ringkasan

Membangun sistem navigasi responsif itu sulit. Sebagian pengguna akan menggunakan keyboard, sebagian akan menggunakan desktop yang canggih, dan sebagian akan mengunjungi dari perangkat seluler kecil. Setiap pengunjung harus dapat membuka dan menutup menu.

Demo tata letak responsif desktop ke seluler
Tema terang dan gelap tidak berfungsi di iOS dan Android

Taktik Web

Dalam eksplorasi komponen ini, saya senang menggabungkan beberapa fitur platform web penting:

  1. CSS :target
  2. grid CSS
  3. Transformasi CSS
  4. Kueri Media CSS untuk area pandang dan preferensi pengguna
  5. JS untuk focus peningkatan UX

Solusi saya memiliki satu sidebar dan hanya beralih saat berada di area pandang "seluler" 540px atau kurang. 540px akan menjadi titik henti kami untuk beralih antara tata letak interaktif seluler dan tata letak desktop statis.

Class pseudo :target CSS

Satu link <a> menetapkan hash URL ke #sidenav-open dan yang lainnya ke kosong (''). Terakhir, elemen memiliki id agar cocok dengan hash:

<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<aside id="sidenav-open">
  …
</aside>

Mengklik setiap link ini akan mengubah status hash URL halaman kita, kemudian dengan pseudo-class, saya menampilkan dan menyembunyikan sidenav:

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

Petak CSS

Sebelumnya, saya hanya menggunakan tata letak dan komponen sidenav posisi tetap atau absolut. Namun, petak dengan sintaksis grid-area memungkinkan kita menetapkan beberapa elemen ke baris atau kolom yang sama.

Tumpukan

Elemen tata letak utama #sidenav-container adalah petak yang membuat 1 baris dan 2 kolom, 1 di antaranya diberi nama stack. Jika ruang terbatas, CSS akan menetapkan semua elemen turunan <main> ke nama petak yang sama, menempatkan semua elemen ke ruang yang sama, dan membuat tumpukan.

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

<aside> adalah elemen animasi yang berisi navigasi samping. Tata letak ini memiliki 2 turunan: penampung navigasi <nav> bernama [nav] dan latar belakang <a> bernama [escape], yang digunakan untuk menutup menu.

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

Sesuaikan 2fr & 1fr untuk menemukan rasio yang Anda sukai untuk overlay menu dan tombol tutup ruang negatifnya.

Demo tentang apa yang terjadi saat Anda mengubah rasio.

Transformasi & transisi 3D CSS

Tata letak kita sekarang ditumpuk pada ukuran area pandang seluler. Hingga saya menambahkan beberapa gaya baru, secara default, elemen ini menutupi artikel kita. Berikut beberapa UX yang saya targetkan di bagian berikutnya:

  • Animasi buka dan tutup
  • Hanya animasikan dengan gerakan jika pengguna menyetujuinya
  • Menganimasikan visibility sehingga fokus keyboard tidak memasuki elemen di luar layar

Saat mulai menerapkan animasi gerakan, saya ingin memprioritaskan aksesibilitas.

Gerakan yang mudah diakses

Tidak semua orang menginginkan pengalaman gerakan slide keluar. Dalam solusi kami, preferensi ini diterapkan dengan menyesuaikan variabel CSS --duration di dalam kueri media. Nilai kueri media ini merepresentasikan preferensi sistem operasi pengguna untuk gerakan (jika tersedia).

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
Demo interaksi dengan dan tanpa durasi yang diterapkan.

Sekarang, saat sidenav kita terbuka dan tertutup, jika pengguna lebih memilih gerakan yang dikurangi, saya akan langsung memindahkan elemen ke dalam tampilan, mempertahankan status tanpa gerakan.

Transisi, transformasi, terjemahan

Sidenav keluar (default)

Untuk menyetel status default sidenav di perangkat seluler ke status di luar layar, saya memosisikan elemen dengan transform: translateX(-110vw).

Perhatikan, saya menambahkan 10vw lain ke kode di luar layar -100vw yang umum, untuk memastikan box-shadow sidenav tidak mengintip ke viewport utama saat disembunyikan.

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);
  }
}
Sidenav masuk

Saat elemen #sidenav cocok sebagai :target, tetapkan posisi translateX() ke 0 homebase, dan lihat saat CSS menggeser elemen dari posisi -110vw keluar, ke posisi "masuk" 0 selama var(--duration) saat hash URL diubah.

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

Visibilitas transisi

Tujuannya sekarang adalah menyembunyikan menu dari pembaca layar saat tidak ada, sehingga sistem tidak memfokuskan menu di luar layar. Saya melakukannya dengan menyetel transisi visibilitas saat :target berubah.

  • Saat masuk, jangan transisikan visibilitas; langsung terlihat agar saya dapat melihat elemen meluncur masuk dan menerima fokus.
  • Saat keluar, transisi visibilitas tetapi tunda, sehingga beralih ke hidden di akhir transisi keluar.

Peningkatan UX aksesibilitas

Solusi ini mengandalkan perubahan URL agar status dapat dikelola. Tentu saja, elemen <a> harus digunakan di sini, dan elemen ini mendapatkan beberapa fitur aksesibilitas yang bagus secara gratis. Mari hiasi elemen interaktif kita dengan label yang secara jelas menyatakan maksud.

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
  <svg>...</svg>
</a>
Demo UX interaksi keyboard dan voiceover.

Sekarang tombol interaksi utama kita menyatakan dengan jelas maksudnya untuk mouse dan keyboard.

:is(:hover, :focus)

Dengan selektor semu fungsional CSS yang praktis ini, kita dapat dengan cepat menyertakan gaya saat mengarahkan kursor dengan membagikannya juga saat fokus.

.hamburger:is(:hover, :focus) svg > line {
  stroke: hsl(var(--brandHSL));
}

Menambahkan JavaScript

Tekan escape untuk menutup

Tombol Escape di keyboard Anda akan menutup menu, bukan? Mari kita hubungkan.

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
Histori browser

Untuk mencegah interaksi buka dan tutup menumpuk beberapa entri ke dalam histori browser, tambahkan JavaScript inline berikut ke tombol tutup:

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

Tindakan ini akan menghapus entri histori URL saat ditutup, sehingga seolah-olah menu tidak pernah dibuka.

UX Fokus

Cuplikan berikutnya membantu kita memfokuskan tombol buka dan tutup setelah tombol tersebut dibuka atau ditutup. Saya ingin mempermudah pengalihan.

sidenav.addEventListener('transitionend', e => {
  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
      ? document.querySelector('#sidenav-close').focus()
      : document.querySelector('#sidenav-button').focus();
})

Saat sidenav terbuka, fokuskan tombol tutup. Saat sidenav ditutup, fokuskan tombol buka. Saya melakukannya dengan memanggil focus() pada elemen di JavaScript.

Kesimpulan

Sekarang setelah Anda tahu cara saya melakukannya, bagaimana Anda?! Hal ini menghasilkan arsitektur komponen yang menarik. Siapa yang akan membuat versi pertama dengan slot? 🙂

Mari kita diversifikasi pendekatan kita dan pelajari semua cara untuk membangun di web. Buat Glitch, tweet versi Anda kepada saya, dan saya akan menambahkannya ke bagian Remix komunitas di bawah.

Remix komunitas