Renk uyumlu ve erişilebilir bir ipucu özel öğesi oluşturmaya dair temel bir genel bakış.
Bu gönderide, renk uyumlu ve erişilebilir bir <tool-tip>
özel öğesi oluşturma hakkındaki düşüncelerimi paylaşmak istiyorum. Demoyu deneyin ve kaynağı görüntüleyin.
Videoyu tercih ediyorsanız bu yayının YouTube sürümünü burada bulabilirsiniz:
Genel Bakış
İpucu, kullanıcı arayüzleriyle ilgili ek bilgiler içeren, modal olmayan, engellemeyen, etkileşimli olmayan bir yer paylaşımıdır. Varsayılan olarak gizlidir ve ilişkili bir öğenin fareyle üzerine gelindiğinde veya odaklandığında gösterilir. İpuçları seçilemez veya doğrudan etkileşim kurulamaz. İpuçları, etiketlerin veya diğer yüksek değerli bilgilerin yerini almaz. Kullanıcılar, ipucu olmadan görevlerini eksiksiz şekilde tamamlayabilmelidir.
Açma/kapatma ucu ve ipucu
Birçok bileşen gibi, ipucu kavramının da farklı açıklamaları vardır. Örneğin, MDN, WAI ARIA, Sarah Higley ve Inclusive Components'de bu kavram farklı şekillerde açıklanır. İpuçları ile açma/kapatma ipuçlarının ayrılması hoşuma gitti. İpucu, etkileşimli olmayan ek bilgiler içermelidir. Açma/kapatma ucu ise etkileşim ve önemli bilgiler içerebilir. Bu ayrımın başlıca nedeni erişilebilirliktir. Kullanıcıların pop-up'a nasıl gideceği, içindeki bilgilere ve düğmelere nasıl erişeceği belirlenmelidir. Açma/kapatma ipuçlarının karmaşıklığı hızla artar.
Designcember sitesinden bir açma/kapatma ipucuna ait videoyu burada bulabilirsiniz. Bu videoda, kullanıcının sabitleyip açabileceği ve keşfedebileceği, ardından ışıklı kapatma düğmesi veya Esc tuşuyla kapatabileceği etkileşimli bir yer paylaşımı gösterilmektedir:
Bu GUI Challenge'da, neredeyse her şeyi CSS ile yapmak için ipucu yoluna gidildi. Bu ipucu nasıl oluşturulur?
Brüt kar
Özel bir öğe <tool-tip>
kullanmayı tercih ettim. Yazarların, istemedikleri takdirde web bileşenlerine özel öğeler eklemesi gerekmez. Tarayıcı, <foo-bar>
öğesini <div>
gibi işler. Daha az spesifik bir sınıf adı gibi özel bir öğeyi düşünebilirsiniz. JavaScript kullanılmaz.
<tool-tip>A tooltip</tool-tip>
Bu, içinde metin bulunan bir div gibidir. [role="tooltip"]
ekleyerek uygun ekran okuyucuların erişilebilirlik ağacına bağlanabiliriz.
<tool-tip role="tooltip">A tooltip</tool-tip>
Ekran okuyucular bu öğeyi artık bir ipucu olarak tanır. Aşağıdaki örnekte, ilk bağlantı öğesinin ağacında tanınan bir ipucu öğesinin nasıl yer aldığına ve ikincisinin nasıl yer almadığına dikkat edin. İkinci kullanıcıda bu rol yoktur. Stil bölümünde bu ağaç görünümünü geliştireceğiz.
Ardından, ipucu metninin odaklanılabilir olmaması gerekir. Ekran okuyucular ipucu rolünü anlamazsa kullanıcıların içeriği okumak için <tool-tip>
'ye odaklanmasına izin verir. Kullanıcı deneyimi için buna gerek yoktur. Ekran okuyucular içeriği üst öğeye ekler. Bu nedenle, erişilebilir hale getirilmesi için odaklanmasına gerek yoktur. Kullanıcıların sekme akışlarında bu ipucu içeriğini yanlışlıkla bulmamasını sağlamak için inert
kullanabiliriz:
<tool-tip inert role="tooltip">A tooltip</tool-tip>
Ardından, ipucu metninin konumunu belirtmek için arayüz olarak özellikleri kullanmayı seçtim. Varsayılan olarak tüm <tool-tip>
öğeleri "üst" konumda olur ancak tip-position
eklenerek öğelerin konumu özelleştirilebilir:
<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>
Bu tür durumlarda, <tool-tip>
öğesine aynı anda birden fazla konum atanmaması için sınıflar yerine özellikler kullanmayı tercih ederim.
Yalnızca bir tane veya hiç olmayabilir.
Son olarak, ipucu sağlamak istediğiniz öğenin içine <tool-tip>
öğeleri yerleştirin. Burada, bir <picture>
öğesinin içine bir resim ve <tool-tip>
yerleştirerek alt
metnini gören kullanıcılarla paylaşıyorum:
<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>
Burada bir <abbr>
öğesinin içine bir <tool-tip>
yerleştiriyorum:
<p>
The <abbr>HTML <tool-tip role="tooltip" tip-position="top">Hyper Text Markup Language</tool-tip></abbr> abbr element.
</p>
Erişilebilirlik
Açma/kapatma ipuçlarını değil, ipuçları oluşturmayı tercih ettiğim için bu bölüm çok daha basit. Öncelikle, istediğimiz kullanıcı deneyimini özetleyeyim:
- Sınırlı alanlarda veya dağınık arayüzlerde ek mesajları gizleyin.
- Kullanıcı bir öğeyle etkileşime geçmek için fareyle üzerine geldiğinde, odaklandığında veya dokunma özelliğini kullandığında mesajı gösterin.
- Fareyle üzerine gelme, odaklanma veya dokunma sona erdiğinde mesajı tekrar gizleyin.
- Son olarak, kullanıcı hareketlerin azaltılmasıyla ilgili bir tercih belirtmişse hareketlerin azaltıldığından emin olun.
Hedefimiz, isteğe bağlı ek mesajlaşmadır. Görme engeli olmayan fare veya klavye kullanıcıları, fareyle mesajın üzerine gelerek mesajı görebilir ve okuyabilir. Görme engelli bir ekran okuyucu kullanıcısı, mesajı göstermek için odaklanabilir ve aracıyla mesajı sesli olarak alabilir.
Önceki bölümde erişilebilirlik ağacını, ipucu rolünü ve etkin olmayan öğeleri ele aldık. Geriye kalan tek şey, bunu test etmek ve kullanıcı deneyiminin ipucu mesajını kullanıcıya uygun şekilde gösterdiğini doğrulamaktır. Test sonucunda, sesli mesajın hangi kısmının ipucu olduğu anlaşılamadı. Erişilebilirlik ağacında da hata ayıklama sırasında görülebilir. "Üst" bağlantı metni, "Bakın, ipuçları" ile birlikte duraksamadan birlikte çalıştırılır. Ekran okuyucu, metni bölemez veya ipucu içeriği olarak tanımlayamaz.
<tool-tip>
öğesine yalnızca ekran okuyuculara yönelik bir sözde öğe ekleyin. Böylece, görme engelli kullanıcılar için kendi istem metnimizi ekleyebiliriz.
&::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;
}
Aşağıda, bağlantı metninin ardından noktalı virgül ve "İpucu var: " istemi bulunan güncellenmiş erişilebilirlik ağacını görebilirsiniz.
Artık ekran okuyucu kullanıcısı bağlantıya odaklandığında "üst" dedikten sonra kısa bir duraklıyor ve "ipucu var: bak, ipuçları"yı duyuruyor. Bu, ekran okuyucu kullanıcılarına birkaç güzel kullanıcı deneyimi ipucu verir. Duraklatma, bağlantı metni ile ipucu arasında güzel bir ayrım sağlar. Ayrıca, "has tooltip" (ipucu var) ifadesi ekran okuyucu kullanıcılarına bildirildiğinde, daha önce duymuş olan kullanıcılar bu ifadeyi kolayca iptal edebilir. Ek mesajı gördüğünüz gibi, fareyle üzerine gelip hızlıca uzaklaşmayı andırır. Bu, kullanıcı deneyimi açısından iyi bir eşleşme gibiydi.
Stiller
<tool-tip>
öğesi, ek mesajı temsil ettiği öğenin alt öğesi olacaktır. Bu nedenle, önce yer paylaşımı efektinin temel özelliklerinden başlayalım. position absolute
ile doküman akışından çıkarın:
tool-tip {
position: absolute;
z-index: 1;
}
Üst öğe bir yığın bağlamı değilse ipucu, kendisini en yakın yığın bağlamına yerleştirir. Bu, istediğimiz bir durum değildir. Blokta size yardımcı olabilecek yeni bir seçici var: :has()
.
:has(> tool-tip) {
position: relative;
}
Tarayıcı desteği konusunda çok fazla endişelenmeyin. Öncelikle, bu ipuçlarının tamamlayıcı olduğunu unutmayın. Çalışmazlarsa sorun olmaz. İkinci olarak, JavaScript bölümünde :has()
desteği olmayan tarayıcılar için ihtiyaç duyduğumuz işlevleri çoklu doldurmak üzere bir komut dosyası dağıtacağız.
Ardından, üst öğelerinden işaretçi etkinliklerini çalmayacakları için ipucu metinlerini etkileşimli olmayan hale getirelim:
tool-tip {
…
pointer-events: none;
user-select: none;
}
Ardından, ipucu geçişini yumuşak geçişle yapabilmemiz için ipucu opaklığını kullanarak gizleyin:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
:is()
ve :has()
, alt ipucu öğesinin görünürlüğünü değiştirmek için kullanıcı etkileşimini anlayan üst öğeler içeren tool-tip
'i bilgilendirerek buradaki ağır işi yapar. Fare kullanıcıları fareyle üzerine gelebilir, klavye ve ekran okuyucu kullanıcıları odaklanabilir, dokunmatik kullanıcılar ise dokunabilir.
Görme engeli olmayan kullanıcılar için gösterme ve gizleme yer paylaşımı çalışmaktadır. Şimdi tema oluşturma, konumlandırma ve balona üçgen şekli eklemeyle ilgili bazı stiller ekleme zamanı. Aşağıdaki stillerde, özel mülkler kullanılmaya başlanır. Bu stiller, şimdiye kadarki deneyimlerimizi temel alır ancak yüzen bir ipucu gibi görünmesi için gölgeler, yazı tipleri ve renkler de ekler:
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;
}
Tema ayarları
Metin rengi, CanvasText
sistem anahtar kelimesi aracılığıyla sayfadan devralındığından ipucu için yönetilecek yalnızca birkaç renk vardır. Ayrıca, değerleri depolamak için özel özellikler oluşturduğumuzdan yalnızca bu özel özellikleri güncelleyebilir ve geri kalanı temanın halletmesine izin verebiliriz:
@media (prefers-color-scheme: light) {
tool-tip {
--_bg: white;
--_shadow-alpha: 15%;
}
}
Açık tema için arka planı beyaza uyarlar ve gölgelerin opaklığını ayarlayarak gölgeleri çok daha az güçlü hale getiririz.
Sağdan sola
Sağdan sola okuma modlarını desteklemek için özel bir özellik, belge yönünün değerini sırasıyla -1 veya 1 değerinde saklar.
tool-tip {
--isRTL: -1;
}
tool-tip:dir(rtl) {
--isRTL: 1;
}
Bu, ipucu konumlandırmaya yardımcı olmak için kullanılabilir:
tool-tip[tip-position="top"]) {
--_x: calc(50% * var(--isRTL));
}
Üçgenin nerede olduğuna yardımcı olmanın yanı sıra:
tool-tip[tip-position="right"]::after {
--_tip: var(--_left-tip);
}
tool-tip[tip-position="right"]:dir(rtl)::after {
--_tip: var(--_right-tip);
}
Son olarak, translateX()
üzerinde mantıksal dönüştürme işlemleri için de kullanılabilir:
--_x: calc(var(--isRTL) * -3px * -1);
İpucu konumlandırma
Hem fiziksel hem de mantıksal ipucu konumlarını yönetmek için inset-block
veya inset-inline
özellikleriyle ipucu konumunu mantıksal olarak ayarlayın. Aşağıdaki kodda, dört konumun her birinin hem soldan sağa hem de sağdan sola yönler için nasıl biçimlendirildiği gösterilmektedir.
Üste ve blok başlangıcına hizalama
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;
}
Sağa ve satır içi uç hizalama
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);
}
Alt ve blok sonu hizalama
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;
}
Sola ve satır içi başlangıç hizalaması
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);
}
Animasyon
Şu ana kadar yalnızca ipucu metninin görünürlüğünü değiştirdik. Bu bölümde, genellikle güvenli olan azaltılmış hareket geçişi nedeniyle önce tüm kullanıcılar için opaklığı animasyonlu olarak değiştireceğiz. Ardından, ipucu üst öğeden kayıyormuş gibi görünmesi için dönüştürme konumunu animasyonlu hale getireceğiz.
Güvenli ve anlamlı bir varsayılan geçiş
İpucu öğesini, opaklık ve dönüşüm geçişi yapacak şekilde şu şekilde biçimlendirin:
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;
}
Geçişe hareket ekleme
Kullanıcı hareketi kabul ediyorsa, ipucu gösterilebilecek her kenar için translateX mülküne hareket edeceği küçük bir mesafe vererek mülkü hafifçe konumlandırın:
@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;
}
}
"in" durumu translateX(0)
olduğu için bunun "out" durumunu ayarladığına dikkat edin.
JavaScript
JavaScript'i isteğe bağlı olarak kullanabilirsiniz. Bunun nedeni, bu ipuçlarından hiçbirinin kullanıcı arayüzünüzde bir görevi tamamlamak için okunması gerekmemesidir. Bu nedenle, ipuçları tamamen başarısız olursa bu çok önemli bir sorun değildir. Bu, ipuçları için aşamalı olarak geliştirme yapabileceğimiz anlamına da gelir. Tüm tarayıcılar :has()
'ü desteklemeye başladığında bu komut dosyası tamamen kaldırılabilir.
Polifill komut dosyası iki işlem yapar ve bunu yalnızca tarayıcı :has()
'ü desteklemiyorsa yapar. Öncelikle :has()
desteği olup olmadığını kontrol edin:
if (!CSS.supports('selector(:has(*))')) {
// do work
}
Ardından, <tool-tip>
öğelerinin üst öğelerini bulun ve bunlara çalışacakları bir sınıf adı verin:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
}
Ardından, aynı davranış için :has()
seçiciyi simüle ederek bu sınıf adını kullanan bir stil grubu ekleyin:
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)
}
Bu kadar. :has()
desteklenmiyorsa artık tüm tarayıcılar ipuçları gösterecektir.
Sonuç
Bunu nasıl yaptığımı öğrendiniz. Siz nasıl yapardınız? 🙂 Açma/kapatma ipuçlarını kolaylaştırmak için popup
API'sini, z-dizin savaşları yaşamamak için üst katmanı ve öğeleri pencerede daha iyi konumlandırmak için anchor
API'sini kullanmayı sabırsızlıkla bekliyorum. O zamana kadar açıklama metinleri hazırlıyorum.
Yaklaşımlarımızı çeşitlendirelim ve web'de uygulama geliştirmenin tüm yollarını öğrenelim.
Bir demo oluşturun, bağlantılarını bana tweetleyin. Ardından, aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.
Topluluk remiksleri
Henüz gösterilecek bir şey yok.
Kaynaklar
- Github'daki kaynak kod