Ringkasan dasar tentang cara membangun modal mini dan mega yang adaptif warna, responsif, serta dapat diakses dengan elemen <dialog>
.
Dalam postingan ini saya ingin berbagi pendapat saya tentang cara membangun aplikasi yang adaptif warna,
modal mini dan mega yang responsif, serta dapat diakses dengan elemen <dialog>
.
Coba demo dan lihat
sumber Google Cloud Anda.
Jika Anda lebih suka menonton video, berikut versi YouTube untuk postingan ini:
Ringkasan
Tujuan
<dialog>
sangat bagus untuk tindakan atau informasi kontekstual dalam halaman. Pertimbangkan kapan
pengalaman pengguna dapat memanfaatkan tindakan halaman yang sama, bukan multi-halaman
tindakan: mungkin karena formulirnya kecil atau satu-satunya tindakan yang diperlukan dari
pengguna adalah konfirmasi
atau batal.
Elemen <dialog>
baru-baru ini menjadi stabil di seluruh browser:
Saya menemukan bahwa ada beberapa elemen yang hilang, jadi di GUI ini Tantangan Saya menambahkan pengalaman developer item yang saya harapkan: peristiwa tambahan, penutupan cahaya, animasi kustom, dan mini dan mega type.
Markup
Dasar-dasar elemen <dialog>
sederhana. Elemen ini akan
disembunyikan secara otomatis dan memiliki gaya bawaan untuk menempatkan konten.
<dialog>
…
</dialog>
Kita dapat meningkatkan garis dasar ini.
Secara tradisional, elemen dialog banyak berbagi dengan modal, dan sering kali bernama
dapat dipertukarkan. Saya memilih kebebasan untuk
menggunakan elemen dialog untuk
pop-up dialog kecil (mini), serta dialog halaman penuh (mega). Saya bernama
yaitu mega dan mini, dengan kedua dialog yang sedikit disesuaikan untuk berbagai kasus penggunaan.
Saya menambahkan atribut modal-mode
agar Anda dapat menentukan jenis:
<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>
Tidak selalu, tetapi umumnya elemen dialog
akan digunakan untuk mengumpulkan beberapa
informasi interaksi. Formulir di dalam elemen dialog dibuat untuk digunakan
bersama-sama.
Sebaiknya Anda memiliki elemen formulir yang menggabungkan konten dialog sehingga
JavaScript dapat mengakses data yang dimasukkan pengguna. Selain itu, tombol di dalam
formulir menggunakan method="dialog"
dapat menutup dialog tanpa JavaScript dan meneruskan
layanan otomatis dan data skalabel.
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
…
<button value="cancel">Cancel</button>
<button value="confirm">Confirm</button>
</form>
</dialog>
Dialog besar
Dialog besar memiliki tiga elemen di dalam formulir:
<header>
,
<article>
,
dan
<footer>
.
Ini berfungsi sebagai kontainer semantik, serta target gaya untuk
presentasi dialog. {i>Header<i} memberi judul modal dan menawarkan penutupan
tombol. Artikel ini ditujukan untuk informasi dan input formulir. Suatu {i>footer<i} berisi
<menu>
dari
tombol tindakan.
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
<header>
<h3>Dialog title</h3>
<button onclick="this.closest('dialog').close('close')"></button>
</header>
<article>...</article>
<footer>
<menu>
<button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</menu>
</footer>
</form>
</dialog>
Tombol menu pertama memiliki
autofocus
dan pengendali peristiwa inline onclick
. Atribut autofocus
akan menerima
fokus saat dialog terbuka, dan menurut saya praktik terbaik adalah menempatkan
tombol {i>cancel<i}, bukan tombol {i>confirm<i}. Hal ini memastikan
bahwa konfirmasi
disengaja dan bukan tidak disengaja.
Dialog mini
Dialog mini sangat mirip dengan mega dialog, hanya saja ada
Elemen <header>
. Ini memungkinkannya menjadi lebih kecil dan lebih inline.
<dialog id="MiniDialog" modal-mode="mini">
<form method="dialog">
<article>
<p>Are you sure you want to remove this user?</p>
</article>
<footer>
<menu>
<button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</menu>
</footer>
</form>
</dialog>
Elemen dialog memberikan fondasi kuat untuk elemen area pandang penuh yang dapat mengumpulkan data dan interaksi pengguna. Hal-hal penting ini dapat membuat beberapa orang interaksi yang menarik dan canggih dalam situs atau aplikasi Anda.
Aksesibilitas
Elemen dialog memiliki aksesibilitas bawaan yang sangat baik. Daripada menambahkan teks fitur seperti yang biasa saya lakukan, banyak yang sudah ada.
Memulihkan fokus
Seperti yang kami lakukan secara manual dalam Membangun sebuah navigasi , penting bahwa membuka dan menutup sesuatu dengan benar menempatkan fokus pada pembukaan dan penutupan yang relevan tombol. Ketika navigasi samping tersebut terbuka, fokus ditempatkan pada tombol tutup. Jika ditekan, fokus dikembalikan ke tombol yang membukanya.
Elemen dialog adalah perilaku default bawaan:
Sayangnya, jika Anda ingin menganimasikan dialog masuk dan keluar, fungsi ini hilang. Di bagian JavaScript, saya akan memulihkan fungsionalitasnya.
Menjebak fokus
Elemen dialog mengelola
inert
untuk Anda pada dokumen. Sebelum inert
, JavaScript digunakan untuk memantau fokus
meninggalkan elemen, di mana titik itu akan mencegat dan memasukkannya kembali.
Setelah inert
, semua bagian dokumen dapat "dibekukan" sehingga mereka
tidak lagi berfokus pada target atau interaktif dengan mouse. Alih-alih menjebak
fokus, fokus dipandu ke satu-satunya bagian interaktif dari dokumen.
Membuka dan otomatis memfokuskan elemen
Secara default, elemen dialog akan menetapkan fokus ke elemen pertama yang dapat difokuskan
di markup dialog. Jika ini bukan elemen terbaik
bagi pengguna untuk {i>default<i},
gunakan atribut autofocus
. Seperti yang dijelaskan sebelumnya, saya menemukan bahwa praktik terbaik
untuk meletakkannya di tombol
{i>cancel<i} dan bukan tombol {i>confirm<i}. Hal ini memastikan bahwa
konfirmasi tersebut disengaja dan bukan tidak disengaja.
Menutup dengan tombol escape
Sebaiknya, permudah aktivitas ini untuk menutup elemen yang berpotensi mengganggu. Untungnya, elemen dialog akan menangani tombol {i>escape<i}, sehingga Anda dari beban orkestrasi.
Gaya
Ada jalur yang mudah untuk menata gaya elemen dialog dan jalur yang sulit. Mudah
jalur dicapai dengan tidak mengubah properti tampilan dialog dan bekerja
dengan keterbatasannya. Saya menempuh jalur yang sulit untuk
menyediakan animasi kustom bagi
membuka dan menutup dialog, mengambil alih properti display
dan lainnya.
Menata Gaya dengan Progres Terbuka
Untuk mempercepat warna adaptif dan konsistensi desain secara keseluruhan, saya sangat membawa library variabel CSS Open Props saya. Di beberapa selain variabel yang disediakan gratis, saya juga mengimpor normalisasi file dan beberapa tombol, keduanya adalah Buka Progres disediakan sebagai impor opsional. Impor ini membantu saya berfokus untuk menyesuaikan dialog dan demo tanpa memerlukan banyak gaya untuk mendukungnya dan membuatnya terlihat bagus.
Menata gaya elemen <dialog>
Memiliki properti tampilan
Perilaku tampilkan dan sembunyikan default pada elemen dialog mengubah tampilan
block
hingga none
. Sayangnya, ini berarti video tidak dapat dianimasikan
ke dalam dan ke luar, hanya ke dalam. Saya ingin menganimasikan baik ke dalam maupun ke luar, dan langkah pertamanya adalah
untuk atur sendiri
display:
dialog {
display: grid;
}
Dengan mengubah, sehingga memiliki, nilai properti tampilan, seperti yang ditunjukkan dalam di atas cuplikan CSS, sejumlah besar gaya perlu dikelola untuk memfasilitasi pengalaman pengguna yang tepat. Pertama, status default dialog adalah tutup. Anda dapat merepresentasikan status ini secara visual dan mencegah dialog menerima interaksi dengan gaya berikut:
dialog:not([open]) {
pointer-events: none;
opacity: 0;
}
Sekarang dialog tidak terlihat dan tidak dapat berinteraksi jika tidak dibuka. Nanti
Saya akan menambahkan beberapa JavaScript untuk mengelola atribut inert
pada dialog, untuk memastikan
bahwa pengguna {i>keyboard<i} dan pembaca layar
juga tidak dapat menjangkau dialog tersembunyi.
Memberikan tema warna adaptif kepada dialog
Sementara color-scheme
mengikutsertakan dokumen Anda ke file yang disediakan browser
tema warna adaptif dengan preferensi sistem terang dan gelap. Saya ingin menyesuaikan
elemen dialog lebih dari itu. Open Props menyediakan beberapa platform
warna yang otomatis beradaptasi dengan
preferensi sistem terang dan gelap, mirip dengan menggunakan color-scheme
. Ini
sangat bagus untuk membuat {i>layer <i}dalam desain dan saya suka menggunakan warna
mendukung tampilan permukaan lapisan ini secara visual. Warna latar belakangnya adalah
var(--surface-1)
; untuk berada di atas lapisan tersebut, gunakan var(--surface-2)
:
dialog {
…
background: var(--surface-2);
color: var(--text-1);
}
@media (prefers-color-scheme: dark) {
dialog {
border-block-start: var(--border-size-1) solid var(--surface-3);
}
}
Warna adaptif lainnya akan ditambahkan nanti untuk elemen turunan, seperti header dan {i>footer<i}. Saya menganggapnya tambahan untuk elemen dialog, tetapi sangat penting dalam membuat desain dialog yang menarik dan dirancang dengan baik.
Ukuran dialog responsif
Secara default, dialog mendelegasikan ukurannya ke kontennya, yang umumnya
bagus. Tujuan saya adalah membatasi
max-inline-size
ke ukuran yang dapat dibaca (--size-content-3
= 60ch
) atau 90% dari lebar area tampilan. Ini
memastikan dialog tidak akan ditampilkan secara {i>edge to edge<i} di perangkat seluler, dan
lebar pada layar {i>desktop<i}
sehingga sulit dibaca. Lalu saya menambahkan
max-block-size
sehingga dialog tidak akan melebihi tinggi halaman. Ini juga berarti
bahwa kita akan
menentukan di mana area dialog yang dapat di-scroll, jika tinggi
elemen dialog.
dialog {
…
max-inline-size: min(90vw, var(--size-content-3));
max-block-size: min(80vh, 100%);
max-block-size: min(80dvb, 100%);
overflow: hidden;
}
Perhatikan bahwa saya memiliki max-block-size
dua kali? Yang pertama menggunakan 80vh
,
unit area pandang. Yang benar-benar saya inginkan adalah menjaga
dialog dalam alur relatif,
untuk pengguna internasional, jadi saya menggunakan
mendukung unit dvb
dalam deklarasi kedua untuk saat menjadi lebih stabil.
Penempatan dialog mega
Untuk membantu memposisikan elemen dialog, ada baiknya Anda menguraikan dua bagian: tampilan latar layar penuh dan penampung dialog. Tampilan latar harus mencakup semuanya, memberikan efek {i>shadow<i} untuk membantu mendukung bahwa dialog ini di depan dan konten di belakang tidak dapat diakses. Penampung dialog bebas untuk berada di tengahnya dan mengambil bentuk apa pun yang dibutuhkan oleh isinya.
Gaya berikut memperbaiki elemen dialog ke jendela, merentangkannya ke setiap jendela
sudut, dan menggunakan margin: auto
untuk menempatkan konten di tengah:
dialog {
…
margin: auto;
padding: 0;
position: fixed;
inset: 0;
z-index: var(--layer-important);
}
Gaya dialog mega seluler
Pada tampilan yang terlihat kecil, saya memberi gaya pada megamodal halaman penuh ini sedikit berbeda. diri
setel margin bawah ke 0
, yang membawa konten dialog ke bagian bawah
area pandang. Dengan beberapa penyesuaian gaya, saya bisa mengubah dialog menjadi
{i>actionsheet<i}, lebih dekat ke
jempol pengguna:
@media (max-width: 768px) {
dialog[modal-mode="mega"] {
margin-block-end: 0;
border-end-end-radius: 0;
border-end-start-radius: 0;
}
}
Penempatan dialog mini
Ketika menggunakan area pandang yang lebih besar seperti pada komputer desktop, saya memilih untuk memosisikan dialog mini di elemen yang memanggil mereka. Untuk melakukannya, saya membutuhkan JavaScript. Anda dapat menemukan teknik yang saya gunakan di sini, tapi saya merasa itu berada di luar cakupan artikel ini. Tanpa JavaScript, dialog mini akan muncul di tengah layar, seperti dialog mega.
Buat yang menarik
Terakhir, tambahkan gaya ke dialog sehingga terlihat seperti permukaan yang halus di atas halaman. Kelembutan dicapai dengan membulatkan sudut dialog. Kedalaman dicapai dengan salah satu bayangan Open Props yang dibuat dengan cermat properti:
dialog {
…
border-radius: var(--radius-3);
box-shadow: var(--shadow-6);
}
Menyesuaikan elemen semu tampilan latar
Saya memilih untuk bekerja sangat ringan dengan latar belakang, hanya menambahkan efek buram dengan
backdrop-filter
ke dialog mega:
dialog[modal-mode="mega"]::backdrop {
backdrop-filter: blur(25px);
}
Saya juga memilih untuk melakukan transisi pada backdrop-filter
, dengan harapan bahwa browser
akan memungkinkan transisi elemen tampilan latar di masa mendatang:
dialog::backdrop {
transition: backdrop-filter .5s ease;
}
Tambahan gaya visual
Saya menyebut bagian ini "tambahan" karena lebih berkaitan dengan elemen dialog demo daripada elemen dialog pada umumnya.
Pembatasan scroll
Saat dialog ditampilkan, pengguna masih dapat menggulir laman di belakangnya, yang tidak saya inginkan:
Biasanya,
overscroll-behavior
akan menjadi solusi saya yang biasa, tetapi menurut
spesifikasi,
ini tidak berpengaruh pada dialog karena ini bukan porta gulir, yaitu
sebuah {i>scroller<i} sehingga tidak
ada yang bisa dicegah. Saya bisa menggunakan
JavaScript untuk mengawasi
peristiwa baru dari panduan ini, seperti "tertutup" dan "dibuka", lalu tekan tombol
overflow: hidden
pada dokumen, atau saya bisa menunggu :has()
menjadi stabil di
semua browser:
html:has(dialog[open][modal-mode="mega"]) {
overflow: hidden;
}
Sekarang, saat dialog mega terbuka, dokumen html memiliki overflow: hidden
.
Tata letak <form>
Selain menjadi elemen yang sangat
penting untuk mengumpulkan interaksi
informasi dari pengguna, saya menggunakannya di sini
untuk meletakkan {i>header<i}, {i>footer<i}, dan
elemen artikel. Dengan tata letak ini, saya ingin menyatakan turunan artikel sebagai
area yang dapat di-scroll. Saya mencapai ini dengan
grid-template-rows
Elemen artikel diberi 1fr
dan formulir itu sendiri memiliki jumlah maksimum yang sama
tinggi sebagai elemen dialog. Menyetel tinggi dan
ukuran baris yang tegas ini adalah
memungkinkan elemen artikel dibatasi dan men-scroll jika melebihi:
dialog > form {
display: grid;
grid-template-rows: auto 1fr auto;
align-items: start;
max-block-size: 80vh;
max-block-size: 80dvb;
}
Menata gaya dialog <header>
Peran elemen ini adalah untuk memberikan judul untuk konten dialog dan tawaran tombol tutup yang mudah ditemukan. Warna ini juga diberi warna permukaan agar tampak seperti berada di belakang konten artikel dialog. Persyaratan ini menghasilkan flexbox penampung, item yang disejajarkan secara vertikal dengan jarak tepi, dan beberapa padding dan celah untuk memberikan ruang pada judul dan tombol tutup:
dialog > form > header {
display: flex;
gap: var(--size-3);
justify-content: space-between;
align-items: flex-start;
background: var(--surface-2);
padding-block: var(--size-3);
padding-inline: var(--size-5);
}
@media (prefers-color-scheme: dark) {
dialog > form > header {
background: var(--surface-1);
}
}
Menata gaya tombol tutup header
Karena demo menggunakan tombol Open Props, tombol tutup disesuaikan menjadi tombol yang berpusat pada ikon bulat seperti:
dialog > form > header > button {
border-radius: var(--radius-round);
padding: .75ch;
aspect-ratio: 1;
flex-shrink: 0;
place-items: center;
stroke: currentColor;
stroke-width: 3px;
}
Menata gaya dialog <article>
Elemen artikel memiliki peran khusus dalam dialog ini: elemen ini adalah ruang yang ditujukan untuk di-scroll jika muncul dialog tinggi atau panjang.
Untuk mencapai hal ini, elemen formulir induk
telah menetapkan beberapa batas maksimum untuk
yang menjadi pembatas untuk elemen artikel ini jika
terlalu tinggi. Setel overflow-y: auto
agar scrollbar hanya ditampilkan saat diperlukan,
berisi scroll di dalamnya dengan overscroll-behavior: contain
, dan sisanya
adalah gaya presentasi kustom:
dialog > form > article {
overflow-y: auto;
max-block-size: 100%; /* safari */
overscroll-behavior-y: contain;
display: grid;
justify-items: flex-start;
gap: var(--size-3);
box-shadow: var(--shadow-2);
z-index: var(--layer-1);
padding-inline: var(--size-5);
padding-block: var(--size-3);
}
@media (prefers-color-scheme: light) {
dialog > form > article {
background: var(--surface-1);
}
}
Menata gaya dialog <footer>
Peran {i>footer<i} adalah untuk berisi menu tombol tindakan. Flexbox digunakan untuk ratakan konten ke akhir sumbu sejajar {i>footer<i}, lalu beberapa spasi ke beri ruang pada tombol-tombol tersebut.
dialog > form > footer {
background: var(--surface-2);
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
justify-content: space-between;
align-items: flex-start;
padding-inline: var(--size-5);
padding-block: var(--size-3);
}
@media (prefers-color-scheme: dark) {
dialog > form > footer {
background: var(--surface-1);
}
}
Menata gaya menu footer dialog
menu
digunakan untuk memuat tombol tindakan untuk dialog. Model ini menggunakan wrapping
tata letak flexbox dengan gap
untuk memberikan ruang di antara tombol. Elemen menu
memiliki padding seperti <ul>
. Saya juga menghapus {i>style<i} itu karena saya tidak membutuhkannya.
dialog > form > footer > menu {
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
padding-inline-start: 0;
}
dialog > form > footer > menu:only-child {
margin-inline-start: auto;
}
Animasi
Elemen dialog sering dianimasikan karena masuk dan keluar dari jendela. Memberikan dialog beberapa gerakan yang mendukung untuk masuk dan keluar ini membantu pengguna mengarahkan diri mereka dalam alurnya.
Biasanya elemen dialog hanya dapat dianimasikan masuk, bukan keluar. Hal ini karena
browser mengalihkan properti display
pada elemen. Sebelumnya, panduan
atur tampilan ke petak, dan jangan pernah menyetelnya ke tidak ada. Hal ini membuka
kemampuan untuk
membuat animasi masuk dan keluar.
Propa Terbuka dilengkapi dengan banyak bingkai utama animasi untuk digunakan, sehingga membuat orkestrasi yang mudah dan dibaca. Berikut ini adalah sasaran animasi dan lapisan pendekatan yang saya ambil:
- Gerakan yang dikurangi adalah transisi default, opasitas sederhana yang perlahan semakin jelas.
- Jika gerakan diperbolehkan, animasi {i>slide<i} dan skala ditambahkan.
- Tata letak seluler yang responsif untuk dialog besar disesuaikan agar dapat digeser keluar.
Transisi default yang aman dan bermakna
Meskipun Props Terbuka dilengkapi dengan keyframe
untuk memudar masuk dan keluar, saya lebih suka cara ini
transisi berlapis sebagai default dengan animasi keyframe sebagai
potensi upgrade. Sebelumnya kita sudah menata visibilitas dialog dengan
opasitas, mengorkestrasi 1
atau 0
bergantung pada atribut [open]
. Kepada
transisi antara 0% dan 100%, memberi tahu browser berapa lama dan apa jenis
{i>easing <i}yang Anda inginkan:
dialog {
transition: opacity .5s var(--ease-3);
}
Menambahkan {i>motion <i}pada transisi
Jika pengguna setuju dengan gerakan, baik dialog mega maupun mini harus bergeser
sebagai pintu masuk mereka, dan
skalakan saat keluar. Anda dapat mencapainya dengan
prefers-reduced-motion
kueri media dan beberapa Proposisi Terbuka:
@media (prefers-reduced-motion: no-preference) {
dialog {
animation: var(--animation-scale-down) forwards;
animation-timing-function: var(--ease-squish-3);
}
dialog[open] {
animation: var(--animation-slide-in-up) forwards;
}
}
Menyesuaikan animasi keluar untuk perangkat seluler
Sebelumnya di bagian gaya visual, gaya dialog besar diadaptasi untuk perangkat seluler perangkat menjadi lebih seperti lembaran aksi, seolah-olah selembar kertas kecil tergelincir naik dari bagian bawah layar dan masih melekat di bagian bawah. Timbangan animasi keluar tidak cocok dengan desain baru ini, dan kita dapat menyesuaikannya dengan beberapa kueri media dan beberapa Open Props:
@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
dialog[modal-mode="mega"] {
animation: var(--animation-slide-out-down) forwards;
animation-timing-function: var(--ease-squish-2);
}
}
JavaScript
Ada beberapa hal yang perlu ditambahkan dengan JavaScript:
// dialog.js
export default async function (dialog) {
// add light dismiss
// add closing and closed events
// add opening and opened events
// add removed event
// removing loading attribute
}
Penambahan ini berasal dari keinginan untuk menutup lampu (mengklik dialog tampilan latar), animasi, dan beberapa peristiwa tambahan untuk pengaturan waktu yang lebih baik data formulir.
Menambahkan tutup terang
Tugas ini mudah dan merupakan tambahan yang
bagus untuk elemen dialog yang tidak
menjadi animasi. Interaksi dicapai dengan menonton klik pada dialog
dan memanfaatkan peristiwa
bergelembung
untuk menilai apa yang diklik, dan hanya akan
close()
jika itu adalah elemen paling atas:
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
}
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
Perhatikan dialog.close('dismiss')
. Peristiwa dipanggil dan string disediakan.
String ini dapat diambil oleh JavaScript lain untuk mendapatkan insight tentang cara
dialog ditutup. Anda akan menemukan bahwa saya juga menyediakan
{i>close<i} setiap kali saya memanggil
fungsi dari berbagai tombol, untuk memberikan konteks ke aplikasi saya tentang
interaksi pengguna.
Menambahkan acara penutupan dan acara yang ditutup
Elemen dialog dilengkapi dengan kejadian tutup: elemen itu muncul seketika ketika
fungsi dialog close()
dipanggil. Karena kita menganimasikan elemen ini,
sebaiknya ada peristiwa sebelum dan sesudah animasi, untuk perubahan untuk mengambil
atau mengatur ulang
bentuk dialog. Saya menggunakannya di sini untuk mengelola penambahan
Atribut inert
pada dialog tertutup, dan dalam demo saya menggunakannya untuk memodifikasi
daftar avatar jika pengguna telah mengirimkan gambar baru.
Untuk melakukannya, buat dua peristiwa baru bernama closing
dan closed
. Selanjutnya
mendengarkan peristiwa
tutup bawaan pada dialog. Dari sini, atur dialog ke
inert
dan mengirim peristiwa closing
. Tugas selanjutnya adalah menunggu
animasi dan transisi agar selesai berjalan pada dialog, lalu kirimkan
Peristiwa closed
.
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent = new Event('closed')
export default async function (dialog) {
…
dialog.addEventListener('close', dialogClose)
}
const dialogClose = async ({target:dialog}) => {
dialog.setAttribute('inert', '')
dialog.dispatchEvent(dialogClosingEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogClosedEvent)
}
const animationsComplete = element =>
Promise.allSettled(
element.getAnimations().map(animation =>
animation.finished))
Fungsi animationsComplete
, yang juga digunakan dalam bagian Membuat toast
, menampilkan promise berdasarkan
penyelesaian animasi dan promise transisi. Inilah alasan dialogClose
adalah metode asinkron
fungsi;
model ini kemudian
await
janji itu kembali dan melangkah maju
dengan percaya diri ke acara tertutup.
Menambahkan acara pembukaan dan yang dibuka
Peristiwa ini tidak mudah ditambahkan karena elemen dialog bawaan tidak menyediakan peristiwa terbuka seperti halnya dengan {i>close<i}. Saya menggunakan MutationObserver untuk memberikan insight tentang perubahan atribut dialog. Dalam pengamat ini, Saya akan memantau perubahan pada atribut buka dan mengelola peristiwa kustom sebagaimana mestinya.
Mirip dengan cara kami memulai acara penutupan dan penutupan, buat dua acara baru
disebut opening
dan opened
. Tempat kita sebelumnya memproses dialog tutup
, kali ini gunakan pengamat mutasi yang dibuat untuk melihat
.
…
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent = new Event('opened')
export default async function (dialog) {
…
dialogAttrObserver.observe(dialog, {
attributes: true,
})
}
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(async mutation => {
if (mutation.attributeName === 'open') {
const dialog = mutation.target
const isOpen = dialog.hasAttribute('open')
if (!isOpen) return
dialog.removeAttribute('inert')
// set focus
const focusTarget = dialog.querySelector('[autofocus]')
focusTarget
? focusTarget.focus()
: dialog.querySelector('button').focus()
dialog.dispatchEvent(dialogOpeningEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogOpenedEvent)
}
})
})
Fungsi callback pengamat mutasi akan dipanggil saat dialog
atribut diubah, menyediakan daftar perubahan sebagai array. Iterasi ulang
atribut berubah, mencari attributeName
terbuka. Berikutnya, periksa
jika elemen memiliki atribut atau tidak: ini memberi tahu apakah dialog atau tidak
telah menjadi terbuka. Jika telah dibuka, hapus atribut inert
, setel fokus
ke elemen yang meminta
autofocus
atau elemen button
pertama yang
ditemukan dalam dialog. Terakhir, mirip dengan penutup
dan peristiwa tertutup, segera mengirim peristiwa pembuka, tunggu animasinya
untuk menyelesaikan, lalu mengirim peristiwa yang dibuka.
Menambahkan acara yang dihapus
Dalam aplikasi web satu halaman, dialog sering ditambahkan dan dihapus berdasarkan rute atau kebutuhan dan status aplikasi lainnya. Anda dapat membersihkan peristiwa atau data saat dialog dihapus.
Anda dapat melakukannya dengan pengamat mutasi lain. Kali ini, alih-alih mengamati atribut pada elemen dialog, kita akan mengamati turunan bagian dan perhatikan elemen dialog yang dihapus.
…
const dialogRemovedEvent = new Event('removed')
export default async function (dialog) {
…
dialogDeleteObserver.observe(document.body, {
attributes: false,
subtree: false,
childList: true,
})
}
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.nodeName === 'DIALOG') {
removedNode.removeEventListener('click', lightDismiss)
removedNode.removeEventListener('close', dialogClose)
removedNode.dispatchEvent(dialogRemovedEvent)
}
})
})
})
Callback pengamat mutasi dipanggil setiap kali turunan ditambahkan atau dihapus
dari isi dokumen. Mutasi spesifik yang sedang
diawasi adalah untuk
removedNodes
yang memiliki
nodeName
dari
berdialog. Jika dialog dihapus, peristiwa klik dan tutup dihapus untuk
mengosongkan memori, dan peristiwa khusus yang dihapus akan dikirim.
Menghapus atribut pemuatan
Untuk mencegah animasi dialog memutar animasi keluar saat ditambahkan ke halaman atau saat pemuatan halaman, atribut pemuatan telah ditambahkan ke dialog. Tujuan skrip berikut menunggu animasi dialog selesai berjalan, lalu menghapus atribut ini. Sekarang dialog bebas untuk dianimasikan masuk dan keluar, dan kita telah secara efektif menyembunyikan animasi yang mengganggu.
export default async function (dialog) {
…
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
Pelajari lebih lanjut masalah mencegah animasi keyframe saat pemuatan halaman di sini.
Semuanya
Berikut ini dialog.js
secara keseluruhan, setelah kami menjelaskan setiap bagian
satu per satu:
// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent = new Event('opened')
const dialogRemovedEvent = new Event('removed')
// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(async mutation => {
if (mutation.attributeName === 'open') {
const dialog = mutation.target
const isOpen = dialog.hasAttribute('open')
if (!isOpen) return
dialog.removeAttribute('inert')
// set focus
const focusTarget = dialog.querySelector('[autofocus]')
focusTarget
? focusTarget.focus()
: dialog.querySelector('button').focus()
dialog.dispatchEvent(dialogOpeningEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogOpenedEvent)
}
})
})
// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.nodeName === 'DIALOG') {
removedNode.removeEventListener('click', lightDismiss)
removedNode.removeEventListener('close', dialogClose)
removedNode.dispatchEvent(dialogRemovedEvent)
}
})
})
})
// wait for all dialog animations to complete their promises
const animationsComplete = element =>
Promise.allSettled(
element.getAnimations().map(animation =>
animation.finished))
// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
const dialogClose = async ({target:dialog}) => {
dialog.setAttribute('inert', '')
dialog.dispatchEvent(dialogClosingEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogClosedEvent)
}
// page load dialogs setup
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
dialog.addEventListener('close', dialogClose)
dialogAttrObserver.observe(dialog, {
attributes: true,
})
dialogDeleteObserver.observe(document.body, {
attributes: false,
subtree: false,
childList: true,
})
// remove loading attribute
// prevent page load @keyframes playing
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
Menggunakan modul dialog.js
Fungsi yang diekspor dari modul diharapkan untuk dipanggil dan meneruskan dialog elemen yang ingin menambahkan peristiwa dan fungsi baru ini:
import GuiDialog from './dialog.js'
const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')
GuiDialog(MegaDialog)
GuiDialog(MiniDialog)
Dengan begitu, kedua dialog tersebut akan diupgrade dengan tombol tutup terang, animasi, memuat perbaikan, dan lebih banyak peristiwa untuk dikerjakan.
Memproses peristiwa kustom baru
Setiap elemen dialog yang diupgrade kini dapat memproses lima peristiwa baru, seperti ini:
MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)
MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)
MegaDialog.addEventListener('removed', dialogRemoved)
Berikut dua contoh penanganan peristiwa tersebut:
const dialogOpening = ({target:dialog}) => {
console.log('Dialog opening', dialog)
}
const dialogClosed = ({target:dialog}) => {
console.log('Dialog closed', dialog)
console.info('Dialog user action:', dialog.returnValue)
if (dialog.returnValue === 'confirm') {
// do stuff with the form values
const dialogFormData = new FormData(dialog.querySelector('form'))
console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))
// then reset the form
dialog.querySelector('form')?.reset()
}
}
Dalam demo yang saya buat dengan elemen dialog, saya menggunakan acara tertutup itu dan data formulir untuk menambahkan elemen avatar baru ke dalam daftar. Pemilihan waktu yang tepat adalah bahwa dialog telah menyelesaikan animasi keluarnya, dan kemudian beberapa skrip dianimasikan di avatar baru. Berkat peristiwa baru ini, mengorkestrasi pengalaman pengguna bisa lebih lancar.
Perhatikan dialog.returnValue
: ini berisi string tutup yang diteruskan saat
peristiwa dialog close()
dipanggil. Dalam peristiwa dialogClosed
, Anda harus
mengetahui apakah dialog telah ditutup, dibatalkan, atau dikonfirmasi. Jika sudah dikonfirmasi,
{i>script<i} kemudian mengambil nilai formulir dan mengatur ulang formulir. {i>Reset<i} ini berguna agar
ketika dialog ditampilkan lagi, dialog tersebut kosong dan siap untuk pengiriman baru.
Kesimpulan
Sekarang setelah Anda tahu bagaimana saya melakukannya, bagaimana Anda akan 🙂
Mari kita diversifikasi pendekatan kami dan mempelajari semua cara untuk membangun di web.
Buat demo, link tweet saya, dan saya akan menambahkannya ke bagian remix komunitas di bawah ini.
Remix komunitas
- @GrimLink dengan 3-in-1 dialog.
- @mikemai2awesome dengan teman yang
remix yang tidak mengubah
display
. - @geoffrich_ dengan Ringan dan bagus Polesan KILIK BESAR.
Resource
- Kode sumber di GitHub
- Avatar Dobel