Ringkasan dasar tentang cara membuat komponen toast adaptif dan aksesibel.
Dalam postingan ini, saya ingin berbagi pemikiran tentang cara membuat komponen toast. Coba demo.
Jika Anda lebih suka menonton video, berikut versi YouTube dari postingan ini:
Ringkasan
Toast adalah pesan singkat yang tidak interaktif, pasif, dan asinkron untuk pengguna. Umumnya, pola ini digunakan sebagai pola masukan antarmuka untuk memberi tahu pengguna tentang hasil suatu tindakan.
Interaksi
Toast tidak seperti notifikasi, peringatan dan perintah karena tidak interaktif; tidak dimaksudkan untuk ditutup atau tetap ada. 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 baik untuk toast karena diumumkan ke pembaca layar. HTML yang benar memberikan dasar yang aman bagi kita untuk ditingkatkan dengan JavaScript dan CSS, dan akan ada banyak JavaScript.
Bersulang
<output class="gui-toast">Item added to cart</output>
Hal ini dapat menjadi lebih
inklusif
dengan menambahkan role="status"
. Hal ini memberikan
penggantian jika browser tidak memberikan peran implisit
pada elemen <output>
sesuai spesifikasi.
<output role="status" class="gui-toast">Item added to cart</output>
Penampung toast
Lebih dari satu toast dapat ditampilkan sekaligus. Untuk mengatur beberapa toast, digunakan sebuah penampung. 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
viewport, dan jika lebih banyak toast ditambahkan, toast akan ditumpuk dari tepi layar tersebut.
Penampung GUI
Penampung toast melakukan semua pekerjaan tata letak untuk menampilkan toast. Elemen ini
fixed
ke area tampilan dan menggunakan properti logis
inset
untuk menentukan tepi
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;
}
Selain memosisikan dirinya sendiri dalam area tampilan, penampung toast adalah penampung
petak yang dapat menyelaraskan dan mendistribusikan toast. Item dipusatkan sebagai
grup dengan justify-content
dan dipusatkan satu per satu dengan justify-items
.
Tambahkan sedikit gap
agar roti panggang tidak saling bersentuhan.
.gui-toast-group {
display: grid;
justify-items: center;
justify-content: center;
gap: 1vh;
}
Toast GUI
Setiap toast memiliki beberapa padding
, beberapa sudut yang lebih lembut dengan
border-radius
,
dan fungsi min()
untuk
membantu penentuan ukuran seluler dan desktop. Ukuran responsif dalam CSS berikut
mencegah toast tumbuh lebih lebar dari 90% viewport atau
25ch
.
.gui-toast {
max-inline-size: min(25ch, 90vw);
padding-block: .5ch;
padding-inline: 1ch;
border-radius: 3px;
font-size: 1rem;
}
Gaya
Setelah tata letak dan pemosisian ditetapkan, tambahkan CSS yang membantu beradaptasi dengan setelan dan interaksi pengguna.
Penampung toast
Toast tidak interaktif, mengetuk atau menggeser toast tidak akan melakukan apa pun, tetapi saat ini toast menggunakan peristiwa pointer. Cegah toast mencuri klik dengan CSS berikut.
.gui-toast-group {
pointer-events: none;
}
Toast GUI
Berikan tema adaptif terang atau gelap pada 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 muncul dengan animasi saat memasuki layar.
Mengakomodasi gerakan yang dikurangi dilakukan dengan menyetel nilai translate
ke 0
secara
default, tetapi memperbarui nilai gerakan ke panjang dalam kueri media preferensi
gerakan . Semua orang mendapatkan beberapa animasi, tetapi hanya beberapa pengguna yang memiliki perjalanan toast
sejauh jarak tertentu.
Berikut adalah keyframe yang digunakan untuk animasi toast. CSS akan mengontrol masuk, menunggu, dan keluar 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)) }
}
Elemen toast kemudian menyiapkan variabel dan mengatur 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 pembaca layar siap, JavaScript diperlukan untuk mengatur pembuatan, penambahan, dan penghapusan toast berdasarkan peristiwa pengguna. Pengalaman developer komponen toast harus minimal dan mudah untuk memulai, seperti ini:
import Toast from './toast.js'
Toast('My first toast')
Membuat grup toast dan toast
Saat modul toast dimuat dari JavaScript, modul tersebut 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 penampung berada di atas penampung 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
}
Fungsi init()
dipanggil secara internal ke modul, menyimpan elemen
sebagai Toaster
:
const Toaster = init()
Pembuatan elemen HTML toast dilakukan dengan fungsi createToast()
. Fungsi ini memerlukan beberapa teks untuk toast, membuat elemen <output>
, menghiasnya 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 banyak toast
JavaScript kini menambahkan penampung ke dokumen untuk memuat toast dan siap menambahkan toast yang dibuat. Fungsi addToast()
mengatur penanganan satu
atau banyak toast. Pertama, periksa jumlah toast dan apakah gerakan sudah sesuai, lalu gunakan informasi ini untuk menambahkan toast atau melakukan beberapa animasi menarik sehingga toast lain tampak "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: masuk, tunggu 3s
, keluar.
flipToast()
dipanggil saat ada toast yang sudah ada, menggunakan teknik
yang disebut FLIP oleh Paul
Lewis. Idenya adalah menghitung perbedaan
posisi penampung, sebelum dan sesudah toast baru ditambahkan.
Anggap saja seperti menandai lokasi Toaster saat ini, lokasi tujuannya, lalu
menganimasikan dari lokasi awalnya ke lokasi saat ini.
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',
})
}
Petak CSS melakukan pengangkatan tata letak. Saat toast baru ditambahkan, petak akan menempatkannya di awal dan mengatur jaraknya dengan toast lainnya. Sementara itu, animasi web digunakan untuk menganimasikan penampung dari posisi lama.
Menggabungkan semua JavaScript
Saat Toast('my first toast')
dipanggil, toast dibuat, ditambahkan ke halaman
(bahkan mungkin penampung dianimasikan untuk mengakomodasi toast baru), promise
dikembalikan dan toast yang dibuat
dipantau untuk
penyelesaian animasi CSS (tiga animasi keyframe) untuk penyelesaian 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 ada di fungsi Promise.allSettled()
dan pemetaan toast.getAnimations()
. Karena saya menggunakan beberapa animasi keyframe
untuk toast, agar yakin bahwa semuanya telah selesai, setiap animasi harus
diminta dari JavaScript dan setiap
finished
promise-nya diamati hingga selesai.
allSettled
berfungsi untuk kita, dan akan diselesaikan setelah semua janjinya
terpenuhi. Menggunakan await Promise.allSettled()
berarti baris kode berikutnya dapat menghapus elemen dengan yakin dan mengasumsikan bahwa toast telah menyelesaikan siklus prosesnya. Terakhir, panggilan resolve()
memenuhi janji Toast tingkat tinggi sehingga
developer dapat membersihkan atau melakukan pekerjaan lain setelah toast ditampilkan.
export default Toast
Terakhir, fungsi Toast
diekspor dari modul, agar skrip lain dapat
mengimpor dan menggunakannya.
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 melakukan pekerjaan pembersihan 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
Sekarang setelah Anda tahu cara saya melakukannya, bagaimana Anda‽ 🙂
Mari kita diversifikasi pendekatan kita dan pelajari semua cara untuk membangun di web. Buat demo, tweet linknya kepada saya, dan saya akan menambahkannya ke bagian remix komunitas di bawah.
Remix komunitas
- @_developit dengan HTML/CSS/JS: demo & kode
- Joost van der Schee dengan HTML/CSS/JS: demo & kode