Jika yang kamu punya hanyalah palu, semuanya tampak seperti paku.
<>Abraham Maslow>
Sangat mudah untuk tetap berpegang pada apa yang Anda ketahui. Saat beralih konten, ini mungkin tujuannya display: none
atau opacity: 0
Dengan beberapa JavaScript. Namun saat ini web jauh lebih 'modern', jadi sekarang mungkin saat yang tepat untuk mendapatkan gambaran menyeluruh tentang berbagai cara untuk beralih konten – API asli apa yang sebenarnya didukung saat ini, kelebihan dan kekurangannya, dan beberapa hal tentang API tersebut yang mungkin tidak Anda ketahui (seperti elemen palsu dan hal tidak jelas lainnya).
Jadi, mari luangkan waktu untuk melihat pengungkapannya (<details>
Dan <summary>
), API Dialog, API Popover, dan banyak lagi. Kami akan mempertimbangkan waktu yang tepat untuk menggunakan masing-masing tergantung kebutuhan Anda. Bersyarat atau tanpa syarat? JavaScript atau HTML/CSS murni? Tidak yakin? Jangan khawatir, kami akan membahas semuanya.
Pengungkapan (<details>
Dan <summary>
)
Kasus penggunaan: Ringkas konten dalam bentuk yang dapat diakses dengan detail konten yang dapat dipertukarkan secara independen, atau sebagai akordeon.
Terjadi perintah rilis, pengungkapan – yang dikenal sebagai elemennya <details>
Dan <summary>
– Ini adalah pertama kalinya kami dapat mengganti konten tanpa menggunakan JavaScript atau meretas kotak centang yang aneh. Namun kurangnya dukungan browser web jelas menghambat fitur-fitur baru pada awalnya, dan fitur ini khususnya hadir tanpa akses keyboard. Jadi saya mengerti bahwa Anda belum pernah menggunakannya sejak dirilis di Chrome versi 12 pada tahun 2011. Tidak terlihat, tidak terpikirkan, bukan?
Berikut rinciannya:
- Ia bekerja tanpa JavaScript (tanpa kompromi apa pun).
- Ini sepenuhnya dapat dirancang tanpa
appearance: none
Atau sesuatu seperti itu. - Anda dapat menyembunyikan tag tanpa penentu semu non-standar.
- Anda dapat menghubungkan beberapa pengungkapan untuk membuat akordeon.
- <>Aaand>…Ini sepenuhnya dapat dipindahkan, pada tahun 2024.
Pengungkapan pengkodean
Yang Anda cari adalah ini:
<details>
<summary>Content summary (always visible)</summary>
Content (visibility is toggled when summary is clicked on)
</details>
Di balik layar, konten tersebut dibungkus dengan elemen semu yang dapat kita identifikasi pada tahun 2024. ::details-content
. Selain itu, ada ::marker
Elemen dummy yang menunjukkan apakah pernyataan itu terbuka atau tertutup, yang dapat kita sesuaikan.
Dengan mengingat hal tersebut, pengungkapannya sebenarnya terlihat seperti ini:
<details>
<summary><::marker></::marker>Content summary (always visible)</summary>
<::details-content>
Content (visibility is toggled when summary is clicked on)
</::details-content>
</details>
Agar daftar terbuka secara default, berikan <details>
itu open
tema, yaitu apa yang terjadi di balik layar ketika pengungkapan dibuka.
<details open> ... </details>
Pengungkapan Desain
Jujur saja: Anda mungkin hanya ingin kehilangan pena yang mengganggu itu. Nah, Anda bisa melakukannya dengan mengatur display
kepemilikan <summary>
Untuk apa pun kecuali list-item
:
summary {
display: block; /* Or anything else that isn't list-item */
}
Alternatifnya, Anda dapat mengedit tag. Faktanya, contoh di bawah ini menggunakan Font Awesome untuk menggantinya dengan ikon lain, namun perlu diingat ::marker
Itu tidak mendukung banyak fitur. Solusi paling fleksibel adalah dengan membungkus konten <summary>
Dalam sebuah elemen dan mendefinisikannya dalam CSS.
<details>
<summary><span>Content summary</span></summary>
Content
</details>
details {
/* The marker */
summary::marker {
content: "\f150";
font-family: "Font Awesome 6 Free";
}
/* The marker when <details> is open */
&[open] summary::marker {
content: "\f151";
}
/* Because ::marker doesn’t support many properties */
summary span {
margin-left: 1ch;
display: inline-block;
}
}
Buat akordeon dengan banyak pengungkapan
Untuk membuat akordeon, sebutkan beberapa pengungkapan (bahkan tidak harus saudara kandung) dengan menggunakan a name
Atribut dan nilai terkait (mirip dengan metode implementasi <input type="radio">
):
<details name="starWars" open>
<summary>Prequels</summary>
<ul>
<li>Episode I: The Phantom Menace</li>
<li>Episode II: Attack of the Clones</li>
<li>Episode III: Revenge of the Sith</li>
</ul>
</details>
<details name="starWars">
<summary>Originals</summary>
<ul>
<li>Episode IV: A New Hope</li>
<li>Episode V: The Empire Strikes Back</li>
<li>Episode VI: Return of the Jedi</li>
</ul>
</details>
<details name="starWars">
<summary>Sequels</summary>
<ul>
<li>Episode VII: The Force Awakens</li>
<li>Episode VIII: The Last Jedi</li>
<li>Episode IX: The Rise of Skywalker</li>
</ul>
</details>
Dengan menggunakan program penyematan, kami bahkan dapat mengonversinya menjadi tab horizontal:
<div> <!-- Flex wrapper -->
<details name="starWars" open> ... </details>
<details name="starWars"> ... </details>
<details name="starWars"> ... </details>
</div>
div {
gap: 1ch;
display: flex;
position: relative;
details {
min-height: 106px; /* Prevents content shift */
&[open] summary,
&[open]::details-content {
background: #eee;
}
&[open]::details-content {
left: 0;
position: absolute;
}
}
}
…atau menggunakan API Pemosisian Jangkar 2024, <>vertikal> Tab (sama seperti HTML):
div {
display: inline-grid;
anchor-name: --wrapper;
details[open] {
summary,
&::details-content {
background: #eee;
}
&::details-content {
position: absolute;
position-anchor: --wrapper;
top: anchor(top);
left: anchor(right);
}
}
}
Jika Anda mencari beberapa ide liar tentang apa yang dapat kita lakukan dengan Popover API di CSS, lihat artikel John Rhea di mana dia membuat game interaktif hanya dengan mengungkapkannya!
Tambahkan fungsi JavaScript
Ingin menambahkan beberapa fungsi JavaScript?
// Optional: select and loop multiple disclosures
document.querySelectorAll("details").forEach(details => {
details.addEventListener("toggle", () => {
// The disclosure was toggled
if (details.open) {
// The disclosure was opened
} else {
// The disclosure was closed
}
});
});
Ciptakan pengungkapan yang dapat diakses
Pengungkapan dapat diakses selama Anda mengikuti beberapa aturan. Misalnya, <summary>
Ini pada dasarnya adalah a <label>
yang berarti kontennya diumumkan oleh pembaca layar saat mereka fokus pada konten tersebut. Jika tidak di sana <summary>
atau <summary>
Bukan anak langsung dari <details>
Agen pengguna kemudian akan membuatkan label untuk Anda yang biasanya menunjukkan “detail” baik secara visual atau dalam teknologi bantu. Browser web lama mungkin bersikeras demikian <>Pertama> Sayang, jadi lebih baik lakukan saja.
Untuk menambah ini, <summary>
Dia punya role
ke button
jadi segala sesuatu yang tidak valid di dalam a <button>
Juga tidak valid di dalam a <summary>
. Ini termasuk judul, jadi Anda bisa <>gaya> A <summary>
sebagai judul, tetapi Anda tidak bisa menyisipkan judul ke dalam file <summary>
.
elemen dialog (<dialog>
)
Kasus penggunaan: media
Sekarang kita memiliki API Popover untuk overlay modeless, saya pikir kita sebaiknya mulai menganggap dialog sebagai modal meskipun show()
Metode ini memungkinkan dialog modeless. Keuntungannya adalah itu popover
Atributnya memiliki lebih dari <dialog>
Elemennya adalah Anda dapat menggunakannya untuk membuat overlay modeless tanpa JavaScript, jadi menurut saya tidak ada manfaatnya lagi untuk dialog modeless yang memerlukan JavaScript. Untuk lebih jelasnya, modal overlay adalah overlay yang membuat dokumen master tidak aktif, sedangkan dengan non-modal overlay, dokumen master tetap interaktif. Ada beberapa fitur lain yang juga dimiliki oleh dialog modal, termasuk:
- latar belakang yang elegan,
- Fokus otomatis pada elemen pertama yang dapat difokuskan di dalamnya
<dialog>
(Atau, sebagai cadangan,<dialog>
Itu sendiri – termasukaria-label
Dalam hal ini), - Jebakan fokus (akibat ketidakcukupan dokumen utama),
- itu
esc
Kuncinya menutup kotak dialog, dan - Kotak dialog dan latar belakangnya dapat dipindahkan. Mengkodekan dan mengaktifkan kotak dialog
Mulailah dengan <dialog>
komponen:
<dialog> ... </dialog>
Itu disembunyikan secara default, dan sepertinya <details>
kita bisa mendapatkannya open
Namun, ketika halaman dimuat, itu bukan modal dalam skenario ini karena tidak memiliki konten interaktif karena tidak dibuka dengan showModal()
.
<dialog open> ... </dialog>
Saya tidak bisa mengatakan saya membutuhkan pekerjaan ini. Sebaliknya, Anda mungkin ingin menampilkan dialog pada beberapa jenis interaksi, seperti mengklik tombol — jadi inilah tombolnya:
<button data-dialog="dialogA">Open dialogA</button>
Tunggu, kenapa kita menggunakan atribut data? Ya, karena kita mungkin ingin menyerahkan pengenal yang memberi tahu JavaScript dialog mana yang harus dibuka, sehingga memungkinkan kita menambahkan fungsionalitas dialog ke semua dialog dalam satu cuplikan, seperti ini:
// Select and loop all elements with that data attribute
document.querySelectorAll("[data-dialog]").forEach(button => {
// Listen for interaction (click)
button.addEventListener("click", () => {
// Select the corresponding dialog
const dialog = document.querySelector(`#${ button.dataset.dialog }`);
// Open dialog
dialog.showModal();
// Close dialog
dialog.querySelector(".closeDialog").addEventListener("click", () => dialog.close());
});
});
Jangan lupa untuk menambahkan kecocokan id
ke <dialog>
Jadi ini terkait dengan <button>
Yang menunjukkan bahwa:
<dialog id="dialogA"> <!-- id and data-dialog = dialogA --> ... </dialog>
Terakhir, sertakan tombol “tutup”:
<dialog id="dialogA">
<button class="closeDialog">Close dialogA</button>
</dialog>
Catatan: <form method="dialog">
(Yang dimilikinya <button>
) atau <button formmethod="dialog">
(dibungkus <form>
) juga menutup kotak dialog.
Bagaimana mencegah pengguliran ketika kotak dialog terbuka
Cegah pengguliran saat formulir terbuka menggunakan satu baris CSS:
body:has(dialog:modal) { overflow: hidden; }
Desain latar belakang dialog
Terakhir, kita memiliki latar belakang untuk mengurangi gangguan dari apa yang ada di bawah lapisan atas (ini hanya berlaku untuk media). Polanya bisa ditimpa, seperti ini:
::backdrop {
background: hsl(0 0 0 / 90%);
backdrop-filter: blur(3px); /* A fun property just for backdrops! */
}
Dalam catatan itu, <dialog>
Hal yang sama juga terjadi border
A background
Dan beberapa padding
yang mungkin ingin Anda atur ulang. Faktanya, Popover berperilaku sama.
Berurusan dengan dialog yang tidak bermodal
Untuk mengimplementasikan dialog non-modal, gunakan:
show()
alih-alihshowModal()
dialog[open]
(menargetkan keduanya) sebagai gantinyadialog:modal
Meskipun seperti yang saya katakan sebelumnya, Popover API tidak memerlukan JavaScript, jadi menurut saya lebih baik menggunakannya untuk overlay modeless.
API Popover (<element popover>
)
Kasus penggunaan: Hamparan tanpa mode
Pop-up, pada dasarnya. Kasus penggunaan yang sesuai mencakup keterangan alat (atau tip pengalih – penting untuk mengetahui perbedaannya), petunjuk pengaturan, pemberitahuan, navigasi pengalih, dan hamparan modeless lainnya di mana Anda tidak ingin kehilangan akses ke dokumen utama. Kasus penggunaan ini jelas berbeda dari kotak dialog, namun popup tetap saja cukup keren. Secara fungsional, mereka mirip dengan kotak dialog saja, tetapi mereka bukan modal dan tidak memerlukan JavaScript.
Pengkodean pop-up
Untuk memulai, popup perlu… id
Dan juga popover
atribut dengan manual
nilai (yang berarti mengklik di luar pop-up tidak menutupnya), yaitu auto
Nilai (klik di luar popup <>Dia melakukannya> Tutup), atau tidak ada nilai (yang artinya sama). Secara semantik, pop-up bisa berupa a <dialog>
.
<dialog id="tooltipA" popover> ... </dialog>
Selanjutnya, tambahkan popovertarget
kata sifat untuk <button>
atau <input type="button">
Kita ingin mengubah visibilitas popup, dengan nilai yang sesuai dengan nilai popup tersebut id
Tema (Ini opsional karena mengklik di luar popup akan tetap menutupnya, kecuali… popover
Sudah menyala manual
):
<dialog id="tooltipA" popover>
<button popovertarget="tooltipA">Hide tooltipA</button>
</dialog>
Tempatkan salah satu tombol ini di dokumen utama, sehingga Anda dapat menampilkan popup. Ini benar, popovertarget
Ini sebenarnya adalah sebuah tombol (kecuali jika Anda menentukan lain menggunakan a popovertargetaction
Atribut yang diterima show
, hide
atau toggle
Sebagai nilainya – lebih lanjut tentang itu nanti).
Desain pop-up
Secara default, popup dipusatkan di lapisan atas (seperti kotak dialog), namun Anda mungkin tidak ingin popup tersebut berada di sana karena bukan modal.
<main>
<button popovertarget="tooltipA">Show tooltipA</button>
</main>
<dialog id="tooltipA" popover>
<button popovertarget="tooltipA">Hide tooltipA</button>
</dialog>
Anda dapat dengan mudah menyeretnya ke sudut menggunakan posisi tetap, tetapi untuk popup tooltip, Anda ingin popup tersebut dikaitkan dengan peluncur yang membukanya. CSS Anchor Positioning membuatnya sangat mudah:
main [popovertarget] {
anchor-name: --trigger;
}
[popover] {
margin: 0;
position-anchor: --trigger;
top: calc(anchor(bottom) + 10px);
justify-self: anchor-center;
}
/* This also works but isn’t needed
unless you’re using the display property
[popover]:popover-open {
...
}
*/
Namun masalahnya adalah Anda harus memberi nama pada semua tautan ini, yang bagus untuk komponen bertab tetapi berlebihan untuk situs web dengan jumlah tooltip yang cukup. Untungnya, kami bisa mencocokkan id
Atribut pada tombol Kepada anchor
Atribut aktif popover
yang tidak didukung dengan baik pada November 2024 tetapi demo ini dapat digunakan:
<main>
<!-- The id should match the anchor attribute -->
<button id="anchorA" popovertarget="tooltipA">Show tooltipA</button>
<button id="anchorB" popovertarget="tooltipB">Show tooltipB</button>
</main>
<dialog anchor="anchorA" id="tooltipA" popover>
<button popovertarget="tooltipA">Hide tooltipA</button>
</dialog>
<dialog anchor="anchorB" id="tooltipB" popover>
<button popovertarget="tooltipB">Hide tooltipB</button>
</dialog>
main [popovertarget] { anchor-name: --anchorA; } /* No longer needed */
[popover] {
margin: 0;
position-anchor: --anchorA; /* No longer needed */
top: calc(anchor(bottom) + 10px);
justify-self: anchor-center;
}
Masalah berikutnya adalah kita mengharapkan tooltips muncul saat diarahkan dan ternyata tidak, yang berarti kita perlu menggunakan JavaScript. Meskipun ini terdengar rumit mengingat kita dapat membuat tooltip dengan lebih mudah ::before
/::after
/content:
popup mengizinkan konten HTML (dalam hal ini tooltip kami sebenarnya adalah toggletips) sementara content:
Hanya menerima teks.
Tambahkan fungsi JavaScript
Yang membawa kita pada hal ini…
Oke, mari kita lihat apa yang terjadi di sini. Pertama, kami menggunakan anchor
Atribut untuk menghindari penulisan blok CSS untuk setiap elemen jangkar. Popovers sangat berfokus pada HTML, jadi mari kita gunakan posisi jangkar dengan cara yang sama. Kedua, kami menggunakan JavaScript untuk menampilkan popup (showPopover()
) pada mouseover
. Terakhir, kami menggunakan JavaScript untuk menyembunyikan pop-up (hidePopover()
) pada mouseout
tetapi tidak jika berisi tautan karena kami jelas ingin tautan tersebut dapat diklik (dalam skenario ini, kami juga melakukannya <>TIDAK> Sembunyikan tombol yang menyembunyikan jendela pop-up).
<main>
<button id="anchorLink" popovertarget="tooltipLink">Open tooltipLink</button>
<button id="anchorNoLink" popovertarget="tooltipNoLink">Open tooltipNoLink</button>
</main>
<dialog anchor="anchorLink" id="tooltipLink" popover>Has <a href="#">a link</a>, so we can’t hide it on mouseout
<button popovertarget="tooltipLink">Hide tooltipLink manually</button>
</dialog>
<dialog anchor="anchorNoLink" id="tooltipNoLink" popover>Doesn’t have a link, so it’s fine to hide it on mouseout automatically
<button popovertarget="tooltipNoLink">Hide tooltipNoLink</button>
</dialog>
[popover] {
margin: 0;
top: calc(anchor(bottom) + 10px);
justify-self: anchor-center;
/* No link? No button needed */
&:not(:has(a)) [popovertarget] {
display: none;
}
}
/* Select and loop all popover triggers */
document.querySelectorAll("main [popovertarget]").forEach((popovertarget) => {
/* Select the corresponding popover */
const popover = document.querySelector(`#${popovertarget.getAttribute("popovertarget")}`);
/* Show popover on trigger mouseover */
popovertarget.addEventListener("mouseover", () => {
popover.showPopover();
});
/* Hide popover on trigger mouseout, but not if it has a link */
if (popover.matches(":not(:has(a))")) {
popovertarget.addEventListener("mouseout", () => {
popover.hidePopover();
});
}
});
Menerapkan latar belakang berjangka waktu (dan popup berurutan)
Pada awalnya, saya yakin pop-up dengan latar belakang adalah sebuah kekeliruan, dengan argumen bahwa pop-up tersebut tidak boleh mengaburkan dokumen utama yang dapat difokuskan. Tapi mungkin beberapa detik tidak apa-apa asalkan kita bisa melanjutkan apa yang kita lakukan tanpa harus menutup apa pun? Setidaknya, menurut saya ini berfungsi baik dengan serangkaian tips orientasi:
<!-- Re-showing ‘A’ rolls the onboarding back to that step -->
<button popovertarget="onboardingTipA" popovertargetaction="show">Restart onboarding</button>
<!-- Hiding ‘A’ also hides subsequent tips as long as the popover attribute equates to auto -->
<button popovertarget="onboardingTipA" popovertargetaction="hide">Cancel onboarding</button>
<ul>
<li id="toolA">Tool A</li>
<li id="toolB">Tool B</li>
<li id="toolC">Another tool, “C”</li>
<li id="toolD">Another tool — let’s call this one “D”</li>
</ul>
<!-- onboardingTipA’s button triggers onboardingTipB -->
<dialog anchor="toolA" id="onboardingTipA" popover>
onboardingTipA <button popovertarget="onboardingTipB" popovertargetaction="show">Next tip</button>
</dialog>
<!-- onboardingTipB’s button triggers onboardingTipC -->
<dialog anchor="toolB" id="onboardingTipB" popover>
onboardingTipB <button popovertarget="onboardingTipC" popovertargetaction="show">Next tip</button>
</dialog>
<!-- onboardingTipC’s button triggers onboardingTipD -->
<dialog anchor="toolC" id="onboardingTipC" popover>
onboardingTipC <button popovertarget="onboardingTipD" popovertargetaction="show">Next tip</button>
</dialog>
<!-- onboardingTipD’s button hides onboardingTipA, which in-turn hides all tips -->
<dialog anchor="toolD" id="onboardingTipD" popover>
onboardingTipD <button popovertarget="onboardingTipA" popovertargetaction="hide">Finish onboarding</button>
</dialog>
::backdrop {
animation: 2s fadeInOut;
}
[popover] {
margin: 0;
align-self: anchor-center;
left: calc(anchor(right) + 10px);
}
/*
After users have had a couple of
seconds to breathe, start the onboarding
*/
setTimeout(() => {
document.querySelector("#onboardingTipA").showPopover();
}, 2000);
Sekali lagi, mari kita bongkar. Pertama, setTimeout()
Petunjuk kualifikasi pertama muncul setelah 2 detik. Kedua, animasi latar belakang sederhana dimainkan yang secara bertahap menghilang di latar belakang dan semua latar belakang berikutnya. Dokumen utama tidak dibuat diam dan latar belakangnya tidak persisten, sehingga perhatian dialihkan ke tip pengaturan tanpa terasa mengganggu.
Ketiga, setiap popup memiliki tombol yang memicu tip penyiapan berikutnya, yang memicu tip lainnya, dan seterusnya, menghubungkannya untuk membuat alur orientasi HTML lengkap. Biasanya, menampilkan pop-up akan menutup pop-up lainnya, namun hal ini tidak terjadi jika dipicu dari dalam pop-up lain. Menampilkan kembali pop-up yang terlihat akan mengembalikan pengaturan ke langkah itu, dan menyembunyikan pop-up akan menyembunyikannya dan semua pop-up berikutnya – meskipun demikian <>Yang> Tampaknya hanya berhasil ketika… popover
Itu sama auto
. Saya tidak begitu memahaminya tetapi memungkinkan saya membuat tombol 'Restart Setup' dan 'Cancel Setup'.
<>Hanya dengan HTML.> Anda dapat menavigasi antar tip menggunakan esc
Dan return
.
Buat popup modal
Dengarkan aku. Jika Anda menyukai HTML-ness popover
Tapi nilai semantiknya <dialog>
baris JavaScript ini dapat membuat dokumen utama menganggur, dan dengan demikian membuat modal popup:
document.querySelectorAll("dialog[popover]").forEach(dialog => dialog.addEventListener("toggle", () => document.body.toggleAttribute("inert")));
Namun, Popovers harus datang <>setelah> Dokumen utama jika tidak, mereka akan melakukannya <>Juga> menjadi lesu. Secara pribadi, itulah yang saya lakukan untuk formulir, karena formulir bukan bagian dari konten halaman.
<body>
<!-- All of this will become inert -->
</body>
<!-- Therefore, the modals must come after -->
<dialog popover> ... </dialog>
<>Aaand>…Dia bernafas
Ya, itu banyak sekali. <>Tetapi>…Saya pikir penting untuk melihat semua API ini bersama-sama sekarang setelah mereka mulai matang, untuk benar-benar memahami apa yang bisa dan tidak bisa digunakan. Sebagai hadiah perpisahan, saya akan meninggalkan Anda dengan versi setiap API yang mendukung transisi:
- Pengungkapan geser
- Kotak dialog pop-up (dengan latar belakang memudar)
- Bilah pop-up geser (navigasi hamburger, mengapa tidak?)
Cara berbeda (dan modern) untuk mengalihkan konten yang awalnya diposting di CSS-Tricks, yang merupakan bagian dari keluarga DigitalOcean. Anda harus mendapatkan buletin.