Ringkasan dasar tentang cara membangun elemen kustom tooltip yang adaptif warna dan dapat diakses.
Dalam postingan ini, saya ingin menyampaikan pendapat tentang cara mem-build elemen kustom <tool-tip>
yang adaptif dan mudah diakses. Coba
demonya dan lihat
sumbernya.
Jika Anda lebih suka video, berikut versi YouTube postingan ini:
Ringkasan
Tooltip adalah overlay non-modal, non-pemblokiran, dan non-interaktif yang berisi informasi tambahan untuk antarmuka pengguna. Elemen ini disembunyikan secara default dan menjadi tidak tersembunyi saat elemen terkait diarahkan atau difokuskan. Tooltip tidak dapat dipilih atau berinteraksi secara langsung. Tooltip bukanlah pengganti label atau informasi bernilai tinggi lainnya, pengguna harus dapat menyelesaikan tugasnya sepenuhnya tanpa tooltip.
Alihkan Tip vs. Tooltip
Seperti banyak komponen, terdapat berbagai deskripsi tentang tooltip, misalnya dalam MDN, WAI ARIA, Sarah Higley, dan Komponen Inklusif. Saya menyukai pemisahan antara tooltip dan tip. Tooltip harus berisi informasi tambahan non-interaktif, sedangkan tombol beralih dapat berisi interaktivitas dan informasi penting. Alasan utama kesenjangan ini adalah aksesibilitas, bagaimana pengguna diharapkan untuk membuka pop-up dan memiliki akses ke informasi dan tombol di dalamnya. Tombol beralih menjadi rumit dengan cepat.
Berikut ini video tombol beralih dari situs Designcember; overlay dengan interaktivitas yang dapat dibuka dan dijelajahi pengguna, lalu ditutup dengan tombol tutup ringan atau tombol escape:
Tantangan GUI ini membahas tooltip, yaitu melakukan hampir semua hal dengan CSS, dan berikut cara membuatnya.
Markup
Saya memilih untuk menggunakan elemen kustom <tool-tip>
. Penulis tidak perlu membuat elemen khusus
menjadi komponen web jika mereka tidak menginginkannya. Browser akan memperlakukan
<foo-bar>
seperti <div>
. Anda bisa menganggap elemen khusus seperti
nama kelas dengan kurang spesifik. Tidak ada JavaScript yang terlibat.
<tool-tip>A tooltip</tool-tip>
Ini seperti div dengan beberapa teks di dalamnya. Kita dapat mengaitkan ke hierarki aksesibilitas
pembaca layar yang mampu dengan menambahkan [role="tooltip"]
.
<tool-tip role="tooltip">A tooltip</tool-tip>
Sekarang, bagi pembaca layar, alat ini dikenali sebagai tooltip. Lihat dalam contoh berikut, bagaimana elemen link pertama memiliki elemen tooltip yang dikenali dalam hierarkinya, sedangkan elemen kedua tidak? Yang kedua tidak memiliki peran itu. Di bagian gaya kita akan memperbaiki tampilan hierarki ini.
Selanjutnya, kita memerlukan tooltip agar tidak dapat difokuskan. Jika pembaca layar tidak
memahami peran tooltip, pengguna akan dapat memfokuskan <tool-tip>
untuk
membaca konten, dan pengalaman pengguna tidak memerlukan hal ini. Pembaca layar
akan menambahkan konten ke elemen induk sehingga tidak memerlukan fokus
agar dapat diakses. Di sini, kita dapat menggunakan inert
untuk memastikan tidak ada pengguna yang
secara tidak sengaja menemukan konten tooltip ini dalam alur tab mereka:
<tool-tip inert role="tooltip">A tooltip</tool-tip>
Saya kemudian memilih menggunakan atribut sebagai antarmuka untuk menentukan posisi
tooltip. Secara default, semua <tool-tip>
akan mengasumsikan posisi "atas", tetapi
posisinya dapat disesuaikan pada elemen dengan menambahkan tip-position
:
<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>
Saya cenderung menggunakan atribut, bukan class untuk hal-hal seperti ini, sehingga
<tool-tip>
tidak dapat memiliki beberapa posisi yang ditetapkan padanya secara bersamaan.
Hanya boleh ada satu atau tidak ada.
Terakhir, tempatkan elemen <tool-tip>
di dalam elemen yang ingin Anda berikan
tooltip-nya. Di sini saya membagikan teks alt
kepada pengguna berpenglihatan normal dengan menempatkan gambar
dan <tool-tip>
di dalam elemen
<picture>
:
<picture>
<img alt="The GUI Challenges skull logo" width="100" src="...">
<tool-tip role="tooltip" tip-position="bottom">
The <b>GUI Challenges</b> skull logo
</tool-tip>
</picture>
Di sini, saya menempatkan <tool-tip>
di dalam elemen
<abbr>
:
<p>
The <abbr>HTML <tool-tip role="tooltip" tip-position="top">Hyper Text Markup Language</tool-tip></abbr> abbr element.
</p>
Aksesibilitas
Karena saya memilih untuk membuat tooltip dan bukan beralih tip, bagian ini jauh lebih sederhana. Pertama, saya akan menjelaskan pengalaman pengguna yang diinginkan:
- Dalam ruang terbatas atau antarmuka yang berantakan, sembunyikan pesan tambahan.
- Saat pengguna mengarahkan kursor, memfokuskan, atau menggunakan sentuhan untuk berinteraksi dengan elemen, tampilkan pesan.
- Saat mengarahkan kursor, memfokuskan, atau menyentuh berakhir, sembunyikan pesan lagi.
- Terakhir, pastikan gerakan apa pun dikurangi jika pengguna telah menentukan preferensi untuk gerakan yang dikurangi.
Tujuan kami adalah pesan tambahan on demand. Pengguna mouse atau keyboard yang dapat melihat pesan dapat mengarahkan kursor untuk membuka pesan, membacanya dengan mata mereka. Pengguna pembaca layar yang tidak berpenglihatan bisa fokus untuk membuka pesan, lalu menerimanya secara audio melalui alat mereka.
Di bagian sebelumnya, kita telah membahas hierarki aksesibilitas, peran tooltip dan inert, yang perlu dilakukan adalah mengujinya dan memverifikasi pengalaman pengguna dengan tepat mengungkapkan pesan tooltip kepada pengguna. Setelah pengujian, kami tidak dapat memastikan bagian mana dari pesan suara yang merupakan tooltip. Kolom ini juga dapat dilihat saat proses debug di hierarki aksesibilitas, teks link "top" dijalankan bersamaan, tanpa ragu-ragu, dengan "Look, tooltips!". Pembaca layar tidak merusak atau mengidentifikasi teks sebagai konten tooltip.
Tambahkan elemen semu hanya pada pembaca layar ke <tool-tip>
dan kita dapat menambahkan
teks perintah kita sendiri untuk pengguna yang tidak memiliki gangguan penglihatan.
&::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
Di bawah ini Anda dapat melihat hierarki aksesibilitas yang diperbarui, yang sekarang memiliki titik koma setelah teks link dan perintah untuk tooltip "Memiliki tooltip: ".
Sekarang, saat pengguna pembaca layar memfokuskan link, pesan akan bertuliskan "top" dan melakukan jeda kecil, lalu mengumumkan "has tooltip: view, tooltips". Ini memberi beberapa petunjuk UX yang bagus kepada pengguna pembaca layar. Keraguan tersebut memberikan pemisahan yang jelas antara teks link dan tooltip. Selain itu, saat "memiliki tooltip" diumumkan, pengguna pembaca layar dapat membatalkannya dengan mudah jika pernah mendengarnya sebelumnya. Hal ini sangat mirip dengan tindakan mengarahkan kursor dan menggeser dengan cepat, seperti yang sudah Anda lihat dalam pesan tambahan ini. Ini terasa seperti paritas UX yang bagus.
Gaya
Elemen <tool-tip>
akan menjadi turunan dari elemen yang mewakili
pesan tambahannya, jadi pertama-tama mari kita mulai dengan dasar-dasar untuk
efek overlay. Keluarkan dari alur dokumen dengan position absolute
:
tool-tip {
position: absolute;
z-index: 1;
}
Jika induk bukan merupakan konteks yang bertumpuk, tooltip akan memosisikan diri ke
konteks terdekat yang ada, dan ini bukan yang kita inginkan. Ada pemilih baru pada blok yang dapat membantu, :has()
:
:has(> tool-tip) {
position: relative;
}
Jangan terlalu khawatir tentang dukungan browser. Pertama, ingatlah bahwa
tooltip ini bersifat tambahan. Jika mereka tidak berhasil, seharusnya tidak apa-apa. Kedua, di bagian JavaScript, kami akan men-deploy skrip untuk mem-polyfill fungsionalitas yang diperlukan untuk browser tanpa dukungan :has()
.
Selanjutnya, mari kita jadikan tooltip non-interaktif agar tidak mencuri peristiwa pointer dari elemen induknya:
tool-tip {
…
pointer-events: none;
user-select: none;
}
Kemudian, sembunyikan tooltip dengan opasitas agar kita dapat mentransisikan tooltip dengan crossfade:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
:is()
dan :has()
melakukan
pekerjaan berat di sini, sehingga membuat tool-tip
yang berisi elemen induk mengetahui
interaktivitas pengguna untuk mengalihkan visibilitas tooltip turunan. Pengguna mouse
dapat mengarahkan kursor, keyboard dan pembaca layar dapat difokuskan, serta menyentuh pengguna.
Karena overlay tampilkan dan sembunyikan berfungsi untuk pengguna berpenglihatan normal, kini saatnya menambahkan beberapa gaya untuk penerapan tema, penempatan, dan penambahan bentuk segitiga ke balon. Gaya berikut mulai menggunakan properti kustom, yang dibangun di tempat kita begitu jauh, tetapi juga menambahkan bayangan, tipografi, dan warna agar terlihat seperti tooltip mengambang:
tool-tip {
--_p-inline: 1.5ch;
--_p-block: .75ch;
--_triangle-size: 7px;
--_bg: hsl(0 0% 20%);
--_shadow-alpha: 50%;
--_bottom-tip: conic-gradient(from -30deg at bottom, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) bottom / 100% 50% no-repeat;
--_top-tip: conic-gradient(from 150deg at top, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) top / 100% 50% no-repeat;
--_right-tip: conic-gradient(from -120deg at right, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) right / 50% 100% no-repeat;
--_left-tip: conic-gradient(from 60deg at left, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) left / 50% 100% no-repeat;
pointer-events: none;
user-select: none;
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
position: absolute;
z-index: 1;
inline-size: max-content;
max-inline-size: 25ch;
text-align: start;
font-size: 1rem;
font-weight: normal;
line-height: normal;
line-height: initial;
padding: var(--_p-block) var(--_p-inline);
margin: 0;
border-radius: 5px;
background: var(--_bg);
color: CanvasText;
will-change: filter;
filter:
drop-shadow(0 3px 3px hsl(0 0% 0% / var(--_shadow-alpha)))
drop-shadow(0 12px 12px hsl(0 0% 0% / var(--_shadow-alpha)));
}
/* create a stacking context for elements with > tool-tips */
:has(> tool-tip) {
position: relative;
}
/* when those parent elements have focus, hover, etc */
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
/* prepend some prose for screen readers only */
tool-tip::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
/* tooltip shape is a pseudo element so we can cast a shadow */
tool-tip::after {
content: "";
background: var(--_bg);
position: absolute;
z-index: -1;
inset: 0;
mask: var(--_tip);
}
/* top tooltip styles */
tool-tip:is(
[tip-position="top"],
[tip-position="block-start"],
:not([tip-position]),
[tip-position="bottom"],
[tip-position="block-end"]
) {
text-align: center;
}
Penyesuaian tema
Tooltip hanya memiliki beberapa warna untuk dikelola karena warna teks diwarisi dari
halaman melalui kata kunci sistem CanvasText
. Selain itu, karena kita membuat properti khusus untuk menyimpan nilai, kita bisa memperbarui properti khusus itu saja dan membiarkan tema menangani sisanya:
@media (prefers-color-scheme: light) {
tool-tip {
--_bg: white;
--_shadow-alpha: 15%;
}
}
Untuk tema terang, kita menyesuaikan latar belakang menjadi putih dan membuat bayangannya kurang kuat dengan menyesuaikan opasitasnya.
Kanan ke kiri
Untuk mendukung mode membaca dari kanan ke kiri, properti kustom akan menyimpan nilai arah dokumen ke dalam nilai -1 atau 1.
tool-tip {
--isRTL: -1;
}
tool-tip:dir(rtl) {
--isRTL: 1;
}
Tindakan ini dapat membantu dalam memosisikan tooltip:
tool-tip[tip-position="top"]) {
--_x: calc(50% * var(--isRTL));
}
Juga membantu di mana segitiga berada:
tool-tip[tip-position="right"]::after {
--_tip: var(--_left-tip);
}
tool-tip[tip-position="right"]:dir(rtl)::after {
--_tip: var(--_right-tip);
}
Terakhir, juga dapat digunakan untuk transformasi logis di translateX()
:
--_x: calc(var(--isRTL) * -3px * -1);
Penempatan tooltip
Posisikan tooltip secara logis dengan properti inset-block
atau inset-inline
untuk menangani posisi tooltip fisik dan logis. Kode
berikut menunjukkan cara penataan gaya masing-masing posisi untuk
arah kiri-ke-kanan dan kanan-ke-kiri.
Perataan atas dan awal blok
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position])) {
inset-inline-start: 50%;
inset-block-end: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))::after {
--_tip: var(--_bottom-tip);
inset-block-end: calc(var(--_triangle-size) * -1);
border-block-end: var(--_triangle-size) solid transparent;
}
Perataan kanan dan inline
tool-tip:is([tip-position="right"], [tip-position="inline-end"]) {
inset-inline-start: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"])::after {
--_tip: var(--_left-tip);
inset-inline-start: calc(var(--_triangle-size) * -1);
border-inline-start: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"]):dir(rtl)::after {
--_tip: var(--_right-tip);
}
Perataan bawah dan ujung blok
tool-tip:is([tip-position="bottom"], [tip-position="block-end"]) {
inset-inline-start: 50%;
inset-block-start: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="bottom"], [tip-position="block-end"])::after {
--_tip: var(--_top-tip);
inset-block-start: calc(var(--_triangle-size) * -1);
border-block-start: var(--_triangle-size) solid transparent;
}
Perataan kiri dan inline-start
tool-tip:is([tip-position="left"], [tip-position="inline-start"]) {
inset-inline-end: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"])::after {
--_tip: var(--_right-tip);
inset-inline-end: calc(var(--_triangle-size) * -1);
border-inline-end: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"]):dir(rtl)::after {
--_tip: var(--_left-tip);
}
Animasi
Sejauh ini kita hanya mengalihkan visibilitas tooltip. Di bagian ini, pertama-tama kita akan menganimasikan opasitas untuk semua pengguna, karena ini adalah transisi gerakan pengurangan yang umumnya aman. Kemudian, kita akan menganimasikan posisi transformasi agar tooltip muncul bergeser keluar dari elemen induk.
Transisi default yang aman dan bermakna
Tata gaya elemen tooltip untuk mengubah opasitas dan transformasi, seperti ini:
tool-tip {
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
Menambahkan gerakan ke transisi
Untuk setiap sisi, sebuah tooltip dapat muncul
@media (prefers-reduced-motion: no-preference) {
:has(> tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: 3px;
}
:has(> tool-tip:is([tip-position="right"], [tip-position="inline-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: -3px;
}
:has(> tool-tip:is([tip-position="bottom"], [tip-position="block-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: -3px;
}
:has(> tool-tip:is([tip-position="left"], [tip-position="inline-start"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: 3px;
}
}
Perhatikan bahwa ini menetapkan status "out", karena status "in" berada di translateX(0)
.
JavaScript
Menurut pendapat saya, JavaScript bersifat opsional. Hal ini karena tooltip
ini tidak memerlukan pembacaan untuk menyelesaikan tugas di UI Anda. Jadi, jika tooltip
benar-benar gagal, seharusnya itu bukan masalah. Hal ini juga berarti kita dapat memperlakukan
tooltip sebagai ditingkatkan secara bertahap. Pada akhirnya, semua browser akan mendukung
:has()
dan skrip ini dapat dihentikan sepenuhnya.
Skrip polyfill melakukan dua hal, dan hanya melakukannya jika browser tidak mendukung :has()
. Pertama, periksa dukungan :has()
:
if (!CSS.supports('selector(:has(*))')) {
// do work
}
Selanjutnya, temukan elemen induk <tool-tip>
dan beri nama class untuk
digunakan:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
}
Selanjutnya, masukkan kumpulan gaya yang menggunakan nama class tersebut, yang menyimulasikan pemilih :has()
untuk perilaku yang sama persis:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
let styles = document.createElement('style')
styles.textContent = `
.has_tool-tip {
position: relative;
}
.has_tool-tip:is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
`
document.head.appendChild(styles)
}
Itu saja, sekarang semua browser dengan senang hati akan menampilkan tooltip jika :has()
tidak
didukung.
Kesimpulan
Setelah Anda mengetahui cara saya melakukannya, bagaimana Anda‽ 🙂 Saya sangat menantikan
popup
API untuk mempermudah beralih tip, lapisan
teratas untuk pertempuran
tanpa indeks z, dan
anchor
API untuk memosisikan berbagai hal di jendela dengan lebih baik. Sementara itu, saya akan
membuat tooltip.
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
Belum ada apa-apa di sini.
Referensi
- Kode sumber di GitHub