Renk uyarlamalı ve erişilebilir bir ipucu özel öğesi oluşturmaya dair temel bilgiler.
Bu yayında, renge uyarlanabilir ve erişilebilir bir <tool-tip>
özel öğesinin nasıl oluşturulacağıyla ilgili 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. İpucu ve açma/kapatma ipuçlarının ayrılması hoşuma gitti. İpucunda etkileşimli olmayan ek bilgiler yer almalıdır. Açma/kapatma ipucu ise etkileşim ve önemli bilgileri içerebilir. Bu bölünmenin birincil nedeni erişilebilirliktir. Kullanıcıların pop-up'ta gezinmesi ve içindeki bilgi ve düğmelere nasıl erişimleri beklenmektedir. Açma/kapatma ipuçlarının karmaşıklığı hızla artar.
Designcember sitesindeki açma/kapatma ipucunun videosunu burada görebilirsiniz. Kullanıcının sabitleyip keşfedebileceği, ardından ışık kapatma veya çıkış tuşu ile kapatabileceği etkileşim içeren bir yer paylaşımı:
Bir ipucuyla sonuçlanan bu GUI Görevi, CSS ile hemen hemen her şeyi yapmaya çalışıyordu. GUI'nin nasıl oluşturulacağı aşağıda açıklanmıştır.
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 artık bu öğeyi ipucu olarak tanır. Aşağıdaki örnekte ilk bağlantı öğesinin ağacında bir ipucu öğesinin nasıl tanındığını, ikincisinin ağacında nasıl tanınmadığını görün? İ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, ipucunun odaklanılabilir olmaması gerekir. Ekran okuyucu, ipucu rolünü
anlamazsa kullanıcıların, içeriği okumak için <tool-tip>
öğesine odaklanmasına izin verir ve kullanıcı deneyiminde buna gerek yoktur. Ekran okuyucular, içeriği üst öğeye ekler ve bu nedenle erişilebilir hale getirmek için odaklanılması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, öğeye 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ı azaltılmış hareket tercihi belirtmişse hareketlerin azaltıldığından emin olun.
Hedefimiz isteğe bağlı ek mesajlardı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 atlatmayı ele aldık. Geriye kalan şey, ipucu mesajını kullanıcıya uygun şekilde test edip kullanıcı deneyimini 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 bozmaz veya ipucu içeriği olarak tanımlamaz.
<tool-tip>
öğesine ekran okuyucunun yalnızca sözde bir öğesi eklediğinizde, görme engelli kullanıcılar için kendi istem metnimizi de 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ını" duyuruyor. Bu, ekran okuyucu kullanıcısı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, "ipucu içeriyor" komutu duyurulduğunda, ekran okuyucu kullanıcısı bunu daha önce duymuşsa kolayca iptal edebilir. Ek mesajı gördüğünüz gibi, fareyle üzerine gelip hızlıca üzerine gelmeyi çok hatırlatır. Bu bize güzel bir kullanıcı deneyimi denkliği geldi.
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, yığın bağlamı değilse ipucu kendisini en yakınına yerleştirir, yani istediğimiz bu 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. Bunlar çalışmazsa sorun olmayacaktır. İ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 çalmamaları için ipuçlarının etkileşimli olmamasını sağlayalım:
tool-tip {
…
pointer-events: none;
user-select: none;
}
Ardından, ipucunu opaklıkla gizleyerek ipucunu çapraz geçişle geçirebiliriz:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
Buradaki işin zor kısmı :is()
ve :has()
tarafından üstlenilerek üst öğe içeren tool-tip
, kullanıcı etkileşiminin farkında olmasını sağlayarak alt ipucunun görünürlüğünü açar/kapatır. Fare kullanıcıları fareyle üzerine gelebilir, klavye ve ekran okuyucu kullanıcıları odaklanabilir ve 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 ekleme için 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 metninde 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ını 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 ayarlar ve opaklıklarını ayarlayarak gölgeleri çok daha 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, ipucunu 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()
üzerindeki mantıksal dönüştürmeler için de kullanılabilir:
--_x: calc(var(--isRTL) * -3px * -1);
İpucu konumlandırma
Hem fiziksel hem de mantıksal ipucu konumlarını işlemek için ipucunu inset-block
veya inset-inline
özellikleriyle mantıksal olarak konumlandırı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ıç hizalaması
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);
}
Alta ve blok ucuna 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 ipucunun görünürlüğünü değiştirdik. Bu bölümde, genellikle güvenli olan azaltılmış hareket geçişi olduğundan ilk olarak 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;
}
}
"Giriş" durumu translateX(0)
olduğundan bu ayarın "out" olarak belirlendiğine 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. İlk olarak :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.
Demo oluşturup beni tweet'le bağlantıları oluşturduğumda bunu aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.
Topluluk remiksleri
Henüz burada görülecek bir şey yok.
Kaynaklar
- Github'daki kaynak kod