Membuat komponen toast

Ringkasan dasar tentang cara mem-build komponen toast yang adaptif dan mudah diakses.

Dalam postingan ini, saya ingin berbagi pemikiran tentang cara membuat komponen toast. Coba demonya.

Demo

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

Ringkasan

Toast adalah pesan singkat non-interaktif, pasif, dan asinkron untuk pengguna. Umumnya, keduanya digunakan sebagai pola masukan antarmuka untuk memberi tahu pengguna tentang hasil suatu tindakan.

Interaksi

Toast tidak seperti notifikasi, pemberitahuan, dan perintah karena tidak interaktif dan tidak dimaksudkan untuk ditutup atau dipertahankan. Notifikasi ditujukan untuk informasi yang lebih penting, pesan sinkron yang memerlukan interaksi, atau pesan tingkat sistem (bukan tingkat halaman). Toast lebih pasif daripada strategi pemberitahuan lainnya.

Markup

Elemen <output> adalah pilihan yang tepat untuk toast karena diumumkan kepada pembaca layar. HTML yang benar memberikan dasar yang aman bagi kita untuk melakukan peningkatan dengan JavaScript dan CSS, dan akan ada banyak JavaScript.

Bersulang

<output class="gui-toast">Item added to cart</output>

Tampilan ini dapat menjadi lebih inklusif dengan menambahkan role="status". Tindakan ini akan memberikan penggantian jika browser tidak memberikan peran implisit per spesifikasi kepada elemen <output>.

<output role="status" class="gui-toast">Item added to cart</output>

Penampung toast

Lebih dari satu toast dapat ditampilkan sekaligus. Untuk mengatur beberapa toast, container digunakan. Penampung ini juga menangani posisi toast di layar.

<section class="gui-toast-group">
  <output role="status">Wizard Rose added to cart</output>
  <output role="status">Self Watering Pot added to cart</output>
</section>

Tata letak

Saya memilih untuk menyematkan toast ke inset-block-end area tampilan, dan jika lebih banyak toast ditambahkan, toast akan ditumpuk dari tepi layar.

Penampung GUI

Penampung toasts melakukan semua tugas tata letak untuk menampilkan toast. fixed berada di area tampilan dan menggunakan properti logis inset untuk menentukan tepi mana yang akan disematkan, ditambah sedikit padding dari tepi block-end yang sama.

.gui-toast-group {
  position: fixed;
  z-index: 1;
  inset-block-end: 0;
  inset-inline: 0;
  padding-block-end: 5vh;
}

Screenshot dengan ukuran kotak dan padding DevTools yang ditempatkan pada elemen container .gui-toast.

Selain memosisikan dirinya sendiri dalam area pandang, penampung toast adalah penampung petak yang dapat meratakan dan mendistribusikan toast. Item dipusatkan sebagai grup dengan justify-content dan dipusatkan satu per satu dengan justify-items. Tambahkan sedikit gap agar toast tidak menyentuh.

.gui-toast-group {
  display: grid;
  justify-items: center;
  justify-content: center;
  gap: 1vh;
}

Screenshot dengan overlay petak CSS di grup toast, kali ini
menandai ruang dan celah di antara elemen turunan toast.

Roti Bakar GUI

Masing-masing toast memiliki beberapa padding, beberapa sudut yang lebih halus dengan border-radius, dan fungsi min() untuk membantu perubahan ukuran seluler dan desktop. Ukuran responsif di CSS berikut mencegah toast menjadi lebih lebar dari 90% area tampilan atau 25ch.

.gui-toast {
  max-inline-size: min(25ch, 90vw);
  padding-block: .5ch;
  padding-inline: 1ch;
  border-radius: 3px;
  font-size: 1rem;
}

Screenshot elemen .gui-toast tunggal, dengan padding dan radius
batas ditampilkan.

Gaya

Setelah menetapkan tata letak dan penentuan posisi, tambahkan CSS yang membantu adaptasi dengan setelan dan interaksi pengguna.

Penampung toast

Toast tidak interaktif, mengetuk atau menggesernya tidak melakukan apa pun, tetapi saat ini menggunakan peristiwa pointer. Cegah toast mencuri klik dengan CSS berikut.

.gui-toast-group {
  pointer-events: none;
}

Roti Bakar GUI

Berikan tema adaptif terang atau gelap ke toast dengan properti kustom, HSL, dan kueri media preferensi.

.gui-toast {
  --_bg-lightness: 90%;

  color: black;
  background: hsl(0 0% var(--_bg-lightness) / 90%);
}

@media (prefers-color-scheme: dark) {
  .gui-toast {
    color: white;
    --_bg-lightness: 20%;
  }
}

Animasi

Toast baru akan menampilkan animasi saat memasuki layar. Mengakomodasi gerakan yang dikurangi dilakukan dengan menyetel nilai translate ke 0 secara default, tetapi memperbarui nilai gerakan ke durasi tertentu dalam kueri media preferensi gerakan . Semua orang mendapatkan animasi, tetapi hanya beberapa pengguna yang harus menempuh jarak jauh.

Berikut adalah keyframe yang digunakan untuk animasi toast. CSS akan mengontrol akses masuk, waktu tunggu, dan keluar dari toast, semuanya dalam satu animasi.

@keyframes fade-in {
  from { opacity: 0 }
}

@keyframes fade-out {
  to { opacity: 0 }
}

@keyframes slide-in {
  from { transform: translateY(var(--_travel-distance, 10px)) }
}

Kemudian, elemen toast akan menyiapkan variabel dan mengorkestrasi keyframe.

.gui-toast {
  --_duration: 3s;
  --_travel-distance: 0;

  will-change: transform;
  animation: 
    fade-in .3s ease,
    slide-in .3s ease,
    fade-out .3s ease var(--_duration);
}

@media (prefers-reduced-motion: no-preference) {
  .gui-toast {
    --_travel-distance: 5vh;
  }
}

JavaScript

Dengan gaya dan HTML yang dapat diakses oleh pembaca layar, JavaScript diperlukan untuk mengorkestrasi pembuatan, penambahan, dan penghancuran toast berdasarkan peristiwa pengguna. Pengalaman developer untuk komponen toast harus minimal dan mudah untuk dimulai, seperti ini:

import Toast from './toast.js'

Toast('My first toast')

Membuat grup toast dan toast

Saat dimuat dari JavaScript, modul toast harus membuat penampung toast dan menambahkannya ke halaman. Saya memilih untuk menambahkan elemen sebelum body, hal ini akan membuat masalah penumpukan z-index tidak mungkin terjadi karena container berada di atas container untuk semua elemen isi.

const init = () => {
  const node = document.createElement('section')
  node.classList.add('gui-toast-group')

  document.firstElementChild.insertBefore(node, document.body)
  return node
}

Screenshot grup toast antara tag head dan body.

Fungsi init() dipanggil secara internal ke modul, yang menyembunyikan elemen sebagai Toaster:

const Toaster = init()

Pembuatan elemen HTML toast dilakukan dengan fungsi createToast(). Fungsi ini memerlukan beberapa teks untuk toast, membuat elemen <output>, menghiasinya dengan beberapa class dan atribut, menetapkan teks, dan menampilkan node.

const createToast = text => {
  const node = document.createElement('output')
  
  node.innerText = text
  node.classList.add('gui-toast')
  node.setAttribute('role', 'status')

  return node
}

Mengelola satu atau beberapa toast

JavaScript kini menambahkan penampung ke dokumen untuk berisi toast dan siap menambahkan toast yang dibuat. Fungsi addToast() melakukan orkestrasi dalam menangani satu atau banyak toast. Pertama, periksa jumlah toast, dan apakah gerakan memenuhi syarat, lalu gunakan informasi ini untuk menambahkan toast atau membuat beberapa animasi menarik sehingga toast lainnya muncul untuk "memberi ruang" untuk toast baru.

const addToast = toast => {
  const { matches:motionOK } = window.matchMedia(
    '(prefers-reduced-motion: no-preference)'
  )

  Toaster.children.length && motionOK
    ? flipToast(toast)
    : Toaster.appendChild(toast)
}

Saat menambahkan toast pertama, Toaster.appendChild(toast) menambahkan toast ke halaman yang memicu animasi CSS: animate in, tunggu 3s, animasikan. flipToast() dipanggil jika sudah ada toast, menggunakan teknik yang disebut FLIP oleh Paul Lewis. Idenya adalah menghitung perbedaan posisi penampung, sebelum dan setelah toast baru ditambahkan. Anggap saja seperti menandai posisi pemanggang roti sekarang, posisinya, lalu menganimasikan dari mana pemanggang roti itu ke tempatnya.

const flipToast = toast => {
  // FIRST
  const first = Toaster.offsetHeight

  // add new child to change container size
  Toaster.appendChild(toast)

  // LAST
  const last = Toaster.offsetHeight

  // INVERT
  const invert = last - first

  // PLAY
  const animation = Toaster.animate([
    { transform: `translateY(${invert}px)` },
    { transform: 'translateY(0)' }
  ], {
    duration: 150,
    easing: 'ease-out',
  })
}

Kisi CSS melakukan pengangkatan tata letak. Saat toast baru ditambahkan, petak akan menempatkannya di awal dan memberinya spasi dengan yang lain. Sementara itu, animasi web digunakan untuk menganimasikan penampung dari posisi lama.

Menggabungkan semua JavaScript

Saat Toast('my first toast') dipanggil, toast akan dibuat, ditambahkan ke halaman (bahkan container mungkin dianimasikan untuk mengakomodasi toast baru), promise akan ditampilkan, dan toast yang dibuat ditonton untuk penyelesaian animasi CSS (tiga animasi keyframe) untuk resolusi promise.

const Toast = text => {
  let toast = createToast(text)
  addToast(toast)

  return new Promise(async (resolve, reject) => {
    await Promise.allSettled(
      toast.getAnimations().map(animation => 
        animation.finished
      )
    )
    Toaster.removeChild(toast)
    resolve() 
  })
}

Saya merasa bagian yang membingungkan dari kode ini adalah dalam fungsi Promise.allSettled() dan pemetaan toast.getAnimations(). Karena saya menggunakan beberapa animasi keyframe untuk toast, untuk mengetahui dengan yakin semuanya telah selesai, masing-masing harus diminta dari JavaScript dan setiap finished janji yang diamati untuk penyelesaiannya. allSettled akan berguna bagi kami, artinya menyelesaikan dirinya sendiri setelah semua janjinya terpenuhi. Menggunakan await Promise.allSettled() berarti baris kode berikutnya dapat dengan yakin menghapus elemen dan menganggap toast telah menyelesaikan siklus prosesnya. Terakhir, memanggil resolve() akan memenuhi promise Toast level tinggi sehingga developer dapat membersihkan atau melakukan tugas lain setelah toast ditampilkan.

export default Toast

Terakhir, fungsi Toast diekspor dari modul, untuk diimpor dan digunakan oleh skrip lain.

Menggunakan komponen Toast

Penggunaan toast, atau pengalaman developer toast, dilakukan dengan mengimpor fungsi Toast dan memanggilnya dengan string pesan.

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

Jika developer ingin membersihkan pekerjaan atau apa pun, setelah toast ditampilkan, mereka dapat menggunakan async dan await.

import Toast from './toast.js'

async function example() {
  await Toast('Wizard Rose added to cart')
  console.log('toast finished')
}

Kesimpulan

Setelah Anda tahu cara saya melakukannya, bagaimana Anda‽ 🙂

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