Renk uyarlanabilir ve erişilebilir bir ipucu özel öğesi oluşturmaya yönelik temel bilgiler.
Bu yayında, renk 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üzleri için 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 üzerine gelindiğinde ya da odaklanıldığında gösterilir. Bir ipucu seçilemez veya doğrudan ipucuyla etkileşimde bulunulamaz. İpuçları, etiketlerin veya diğer yüksek değerli bilgilerin yerine geçmez. Kullanıcı, ipucu olmadan görevini tam olarak tamamlayabilmelidir.
İpucu ve İpucu Karşılaştırması
Birçok bileşen gibi ipucunun ne olduğuyla ilgili, örneğin MDN, WAI ARIA, Sarah Higley ve Kapsayıcı Bileşenler'de çeşitli açıklamalar vardır. İpuçları ile geçiş ipuçlarının birbirinden ayrılmasını seviyorum. İpucu, etkileşimli olmayan ek bilgiler içerirken bir açma/kapatma ipucu, etkileşim ve önemli bilgiler içerebilir. Farklılığın birincil nedeni erişilebilirliktir. Kullanıcıların pop-up'a nasıl gitmesi ve içindeki bilgi ile düğmelere nasıl erişebilmesi beklenmektedir. Açma/kapatma ipuçları kısa sürede karmaşık hale gelir.
Burada Designcember sitesindeki açma/kapatma ipucunu içeren bir videoyu görebilirsiniz. Kullanıcının sabitleyip keşfedebileceği, ardından ışığı kapatma veya Escape tuşu ile kapatabileceği etkileşimin yer aldığı bir yer paylaşımı:
Bu GUI Meydan Okuması, CSS ile hemen hemen her şeyi yapmayı amaçlayan bir ipucundan ibarettir. Şimdi, bunu nasıl oluşturacağınızı öğrenebilirsiniz.
Markup
<tool-tip>
özel öğesi kullanmayı tercih ettim. Yazarların istemedikleri takdirde özel öğeler
oluşturmasına gerek yoktur. Tarayıcı, <foo-bar>
öğesini bir <div>
gibi işler. Özel öğeyi daha az belirgin bir sınıf
adı gibi düşünebilirsiniz. Bu süreçte JavaScript kullanılmaz.
<tool-tip>A tooltip</tool-tip>
Bu, içinde metin olan bir div gibidir. [role="tooltip"]
özelliğini ekleyerek uygun ekran okuyucuların erişilebilirlik ağacını birbirine bağlayabiliriz.
<tool-tip role="tooltip">A tooltip</tool-tip>
Artık ekran okuyucularda bu özellik bir ipucu olarak görülüyor. Aşağıdaki örnekte, birinci bağlantı öğesinin ağacında tanınan bir ipucu öğesi varken ikinci öğenin nasıl olmadığı gösterilmiştir? İkincisi bu role sahip değildir. Stiller 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çerikleri okumak için <tool-tip>
öğesine odaklanmasını sağlar ve kullanıcı deneyimi buna ihtiyaç duymaz. Ekran okuyucular, içeriği üst öğeye ekler ve dolayısıyla içeriğin erişilebilir hale getirilmesi gerekmez. Burada, hiçbir kullanıcının sekme akışında bu ipucu içeriğini yanlışlıkla bulamayacağından emin olmak için inert
kullanabiliriz:
<tool-tip inert role="tooltip">A tooltip</tool-tip>
Daha sonra, ipucunun konumunu belirtmek için arayüz olarak özellikleri
kullanmayı seçtim. Varsayılan olarak tüm <tool-tip>
'ler bir "en üst" konumu üstlenir, ancak konum tip-position
eklenerek bir öğe üzerinde özelleştirilebilir:
<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>
Bunun gibi şeyler için sınıf yerine özellikleri kullanma eğilimindeyim. Böylece <tool-tip>
, aynı anda ona birden fazla konum atanamaz.
Yalnızca bir tane olabilir veya hiç olmayabilir.
Son olarak, ipucu sağlamak istediğiniz öğenin içine <tool-tip>
öğeleri yerleştirin. Burada alt
metnini, bir <picture>
öğesinin içine resim ve bir <tool-tip>
yerleştirerek 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 <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
Geçiş ipuçları değil de ipuçları oluşturmayı tercih ettiğim için bu bölüm çok daha basit. Öncelikle, beklediğimiz kullanıcı deneyimini ana hatlarıyla belirteyim:
- Kısıtlı alanlarda veya karmaşık arayüzlerde ek mesajları gizleyin.
- Kullanıcı bir öğenin üzerine geldiğinde, öğeyle etkileşim kurmak için öğeye odaklandığında veya dokunmayı kullandığında mesajı açığa çıkarın.
- Fareyle, odaklama veya dokunma sona erdiğinde iletiyi tekrar gizleyin.
- Son olarak, kullanıcı daha az hareket seçeneğini belirtmişse hareketin azaltıldığından emin olun.
Amacımız isteğe bağlı ek mesajlaşma sağlamaktır. Gören bir fare veya klavye kullanıcısı, fareyle iletinin üzerine gelerek iletiyi gözleriyle okuyabilir. Görme güçlüğü çeken bir ekran okuyucu kullanıcısı, aracı üzerinden işiterek mesajı almaya odaklanarak mesajı görüntüleyebilir.
Önceki bölümde erişilebilirlik ağacını, ipucu rolünü ve haksızlığı ele aldık. Geriye kalan tek şey test edip kullanıcı deneyiminin, ipucu mesajını kullanıcıya uygun şekilde gösterdiğini doğrulamaktır. Yapılan testler sonucunda, sesli mesajın hangi bölümünün araç ipucu olduğu net değil. Erişilebilirlik ağacında hata ayıklarken de görülebilir. "Üst" bağlantı metni, hiç tereddüt etmeden "Bak, araç ipuçları!" ile birlikte çalıştırılır. Ekran okuyucu metni bölmez veya ipucu içeriği olarak tanımlamaz.
<tool-tip>
öğesine yalnızca sözde bir ekran okuyucu öğesi eklediğinizde, 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, artık bağlantı metninden sonra noktalı virgül bulunan ve "Araç ipucu içeriyor: " ipucunu içeren güncellenmiş erişilebilirlik ağacını görebilirsiniz.
Artık, ekran okuyucu kullanıcıları bağlantıya odaklandığında "top" (üstte) yazıp kısa bir süre durdurduktan sonra "iç ipucu: bak, araç ipuçları var" ifadesini duyurur. Bu, ekran okuyucu kullanıcısına kullanıcı deneyimiyle ilgili birkaç iyi ipucu verir. Bu tereddüt, bağlantı metni ile ipucunu kolayca ayırıyor. Ayrıca, "ipucu var" duyurulduğunda, bir ekran okuyucu kullanıcısı daha önce duymuşsa bunu kolayca iptal edebilir. Ek mesajı daha önce gördüğünüz gibi, fareyle üzerine gelme ve öğelerin üzerinde beklemeyi hızlıca geri alma işlemlerini hatırlatırız. Bu bana kullanıcı deneyimi eşdeğeri gibi geldi.
Stiller
<tool-tip>
öğesi, temsil ettiği ek mesajı temsil eden öğenin alt öğesi olacaktır. Bu yüzden, öncelikle yer paylaşımı efektiyle ilgili temel bilgilerle başlayalım. position absolute
ile dokümanı doküman akışının dışına çıkarın:
tool-tip {
position: absolute;
z-index: 1;
}
Üst öğe yığın bağlam değilse ipucu, kendisini en yakın olana konumlandırır. Bu, istemediğimiz bir şeydir. Blok üzerinde size yardımcı olabilecek yeni bir seçici var: :has()
:
:has(> tool-tip) {
position: relative;
}
Tarayıcı desteği konusunda çok endişelenmeyin. Öncelikle, bu ipuçlarının
ek niteliğinde olduğunu unutmayın. Bunlar işe yaramazsa sorun yok demektir. İkinci olarak, JavaScript bölümünde :has()
desteği olmayan tarayıcılarda ihtiyaç duyduğumuz işlevlerin çoklu doldurabilmesi için bir komut dosyası dağıtacağız.
Ardından, üst öğelerinden işaretçi etkinlikleri çalmamaları için ipuçlarını etkileşimli olmayacak şekilde yapalım:
tool-tip {
…
pointer-events: none;
user-select: none;
}
Ardından, ipucunu çapraz geçişle geçirebilmemiz için ipucunu opaklıkla gizleyin:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
Burada işin zor kısmını :is()
ve :has()
yapıyor. Bu nedenle, üst öğeler içeren tool-tip
, alt ipucunun görünürlüğünü açıp kapatabileceği konusunda kullanıcı etkileşiminin farkındadır. Fare kullanıcıları fareyle üzerine gelip klavye ve ekran okuyucu kullanıcıları odaklanabilir ve kullanıcılar buna dokunabilir.
Gören kullanıcılar için "göster ve gizle" yer paylaşımıyla birlikte, üçgen şekli oluşturmak, konumlandırmak ve balona eklemek için bazı stiller eklemenin zamanı geldi. Aşağıdaki stiller, özel özellikleri kullanmaya başlar ve çok uzağa geldiğimiz noktalara eklemeler yapar. Ayrıca, kayan bir ipucu gibi görünmesi için gölgeler, yazı tipi ve renkler 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 düzenlemeleri
Metin rengi sayfadan CanvasText
sistem anahtar kelimesi aracılığıyla devralındığından ipucunda yalnızca birkaç renk yönetilir. Ayrıca, değerleri depolamak için özel özellikler oluşturduğumuzdan yalnızca bu özel özellikleri güncelleyebilir ve gerisini temaya bırakabiliriz:
@media (prefers-color-scheme: light) {
tool-tip {
--_bg: white;
--_shadow-alpha: 15%;
}
}
Açık tema için arka planı beyaza uyarlıyoruz ve opaklıklarını ayarlayarak gölgeleri çok daha az güçlü hale getiriyoruz.
Sağdan sola
Sağdan sola okuma modlarını desteklemek için özel bir özellik, belge yönü değerini sırasıyla -1 veya 1 değerine kaydeder.
tool-tip {
--isRTL: -1;
}
tool-tip:dir(rtl) {
--isRTL: 1;
}
Bu, ipucunun konumlandırılmasına yardımcı olmak için kullanılabilir:
tool-tip[tip-position="top"]) {
--_x: calc(50% * var(--isRTL));
}
Ayrıca üçgenin nerede olduğuna dair yardım:
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üşümler 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 kod, dört konumun her birinin hem soldan sağa hem de sağdan sola yönler için nasıl biçimlendirildiğini gösterir.
Üst 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 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 uç hizalaması
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);
}
Animasyonlar
Şu ana kadar yalnızca ipucunun görünürlüğünü değiştirdik. Bu bölümde, genel olarak güvenli bir azaltılmış hareket geçişi olduğundan, ilk olarak tüm kullanıcılar için opaklığı animasyon haline getireceğiz. Ardından, ipucunun üst öğeden kaymış gibi görünmesi için dönüştürme konumuna animasyon uygularız.
Güvenli ve anlamlı bir varsayılan geçiş
Opaklığı ve dönüşümü geçirmek için ipucu öğesinin stilini şu şekilde değiştirin:
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
Kenarların her biri için ipucu görünebilir. Kullanıcı, hareket etmeyi kabul ediyorsa çeviriX özelliğini şuralardan biraz uzaklaşarak 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 "out" durumu ayarlanıyor.
JavaScript
Bana göre, JavaScript isteğe bağlı. Bunun nedeni, kullanıcı arayüzünüzde bir görevi tamamlamak için bu ipuçlarından hiçbirinin okunması gerekmemesidir. Bu nedenle, ipuçları
tamamen başarısız olursa, sorun yoktur. Bu, ipuçlarını da kademeli olarak
geliştirilmiş olarak değerlendirebileceğimiz anlamına gelir. Sonunda tüm tarayıcılar :has()
özelliğini destekleyecek ve bu komut dosyası tamamen kaldırılabilir.
Çoklu dolgu komut dosyası iki şey yapar ve bunu yalnızca, tarayıcı :has()
özelliğini desteklemiyorsa yapar. İlk olarak :has()
desteğini kontrol edin:
if (!CSS.supports('selector(:has(*))')) {
// do work
}
Daha sonra, <tool-tip>
öğelerinin üst öğelerini bulun ve üzerinde ç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'))
}
Daha sonra, :has()
seçicisini tam olarak aynı davranış için 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)
}
Hepsi bu kadar. Artık :has()
desteklenmiyorsa tüm tarayıcılar ipuçlarını memnuniyetle gösterir.
Sonuç
Nasıl yapıldığını öğrendiğinize göre siz ne yapardınız? 🙂; açma/kapatma ipuçlarını kolaylaştıran popup
API'yi, Z-endeksi savaşlarını ortadan kaldıran üst katman ve öğeleri pencereye daha iyi konumlandırmaya yönelik anchor
API'yi sabırsızlıkla bekliyorum. O zamana kadar size
ipuçları vereceğim.
Yaklaşımlarımızı çeşitlendirelim ve web'de geliştirme yapmanın tüm yollarını öğrenelim.
Bir demo oluşturun, bana tweet atın bağlantıları, 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 kodu