Ders 10 / 27
Klavye Erişimi
Klavyeyle çalıştırılabilirliğin gerekleri, odak sırasıyla görsel sıranın karşılaştırılması, odak kuşatmasının tuzağa dönüştüğü sınır ve tek karakterli kısayolların koşulu.
İçindekiler
Önceki dersin kural denetimi bir satırı açık bıraktı: düğme rolü bildirilmiş ama odak almayan öge. Bildirim doğruydu, davranış yoktu. Bu ayrım iki farklı ölçütün konusudur ve ikincisi bu dersin çıkış noktasıdır.
Klavye erişimi yalnızca klavye kullanan bir azınlığın sorunu değildir. Ekran okuyucular sayfayı klavye arayüzü üzerinden dolaşır; ses komutuyla ve anahtar denetimiyle çalışan araçlar da aynı arayüzü kullanır. Klavyeyle çalışmayan bir bileşen bu araçların hiçbirinde çalışmaz. Buradan pratik bir sonuç çıkar: klavye denetimi, yardımcı teknoloji denemesinden önce yapılabilecek en ucuz ve en geniş kapsamlı sınamadır.
Ölçüt Ne İster
2.1.1 ölçütü, içeriğin bütün işlevlerinin bir klavye arayüzü üzerinden çalıştırılabilmesini ister. İfadenin iki ayrıntısı önemlidir. Birincisi “klavye arayüzü” denmesi: fiziksel bir klavye değil, o arayüzü kullanan her araç kastedilir. İkincisi, ölçütün tek tek tuş vuruşları için belirli bir zamanlama gerektirmemeyi de istemesidir; belirli bir hızda basılması gereken bir tuş dizisi ölçütü karşılamaz.
Tek istisna, girdinin yolunun kendisinin bilgi taşıdığı işlevlerdir — serbest el çizimi gibi. Bir haritayı sürükleyerek kaydırmak bu istisnaya girmez, çünkü aynı işlev kaydırma düğmeleriyle de sağlanabilir.
2.1.2 ölçütü zincirin diğer ucudur: klavyeyle bir bileşene girilebiliyorsa, aynı arayüzle çıkılabilmelidir. Bu iki ölçüt birlikte bir bütün kurar; birincisi girişi, ikincisi çıkışı güvence altına alır.
Odak Sırası Görsel Sırayla Uyuşmalı
2.4.3 ölçütü, odaklanabilir ögelerin anlamı ve çalıştırılabilirliği koruyan bir sırada gezilmesini ister. Sekme sırası belge sırasından gelir; görsel sıra sunum katmanından gelir. İkisi ayrıştığında klavye kullanıcısı ekranda ileri geri sıçrar.
Ölçüm listesi sayfasında bu ayrışma iki nedenle oluşuyor: süzgeç paneli işaretlemeye
sonradan, tablodan sonra eklendi ve sunum katmanı onu tablonun üstüne yerleştirdi; dışa
aktarma düğmesine de “önce gelsin” niyetiyle artı bir tabindex değeri yazıldı.
// odaksira.mjs — sekme sirasi ile gorsel siranin karsilastirilmasi (2.4.3) // gorsel: [satir, sutun] — sunum katmaninin yerlestirdigi yer const SAYFA = [ { ad: "a.atlama-bagi", belge: 1, tabindex: null, gorsel: [0, 1] }, { ad: "button#sirala-tarih", belge: 2, tabindex: null, gorsel: [3, 1] }, { ad: "button#sirala-sicaklik", belge: 3, tabindex: null, gorsel: [3, 2] }, { ad: "button.sil[T-01]", belge: 4, tabindex: null, gorsel: [4, 1] }, { ad: "input#suz-kod", belge: 5, tabindex: null, gorsel: [1, 1] }, { ad: "input#suz-uyari", belge: 6, tabindex: null, gorsel: [1, 2] }, { ad: "button#suz", belge: 7, tabindex: null, gorsel: [1, 3] }, { ad: "button#disa-aktar", belge: 8, tabindex: 1, gorsel: [2, 1] }, ]; // M14/K04'te kurulan kural: once artan tabindex, sonra belge sirasi function sekmeSirasi(ogeler) { const pozitif = ogeler.filter((o) => (o.tabindex ?? 0) > 0) .sort((a, b) => a.tabindex - b.tabindex || a.belge - b.belge); const sifir = ogeler.filter((o) => (o.tabindex ?? 0) === 0 || o.tabindex === null) .sort((a, b) => a.belge - b.belge); return [...pozitif, ...sifir]; } const gorselSira = (ogeler) => [...ogeler].sort((a, b) => a.gorsel[0] - b.gorsel[0] || a.gorsel[1] - b.gorsel[1]); function tersDusenler(ogeler) { const sekme = sekmeSirasi(ogeler).map((o) => o.ad); const gorsel = gorselSira(ogeler).map((o) => o.ad); const ciftler = []; for (let i = 0; i < gorsel.length; i++) { for (let j = i + 1; j < gorsel.length; j++) { if (sekme.indexOf(gorsel[i]) > sekme.indexOf(gorsel[j])) ciftler.push([gorsel[i], gorsel[j]]); } } return ciftler; } function rapor(baslik, ogeler) { console.log(baslik); const sekme = sekmeSirasi(ogeler); const gorsel = gorselSira(ogeler); console.log(" no sekme sirasi gorsel sira"); for (let i = 0; i < sekme.length; i++) { console.log(` ${String(i + 1).padStart(2)} ${sekme[i].ad.padEnd(24)} ${gorsel[i].ad}`); } const ciftler = tersDusenler(ogeler); console.log(` tersine dusen cift sayisi: ${ciftler.length}`); for (const [a, b] of ciftler.slice(0, 4)) { console.log(` "${a}" gorselde once, sekmede "${b}" ogesinden sonra`); } } rapor("suzgec paneli kaynakta tablodan sonra, tabindex=1 var:", SAYFA); // Duzeltme: suzgec paneli kaynakta yukari alinir, arti tabindex kaldirilir const DUZELTILMIS = SAYFA.map((o) => { const yeni = { ...o, tabindex: null }; if (o.ad.startsWith("input#suz") || o.ad === "button#suz") yeni.belge = o.belge - 3; if (o.ad === "button#disa-aktar") yeni.belge = 5; if (o.ad.startsWith("button#sirala") || o.ad.startsWith("button.sil")) yeni.belge = o.belge + 4; return yeni; }); console.log(""); rapor("suzgec paneli kaynakta yukari alindi, tabindex kaldirildi:", DUZELTILMIS);
suzgec paneli kaynakta tablodan sonra, tabindex=1 var:
no sekme sirasi gorsel sira
1 button#disa-aktar a.atlama-bagi
2 a.atlama-bagi input#suz-kod
3 button#sirala-tarih input#suz-uyari
4 button#sirala-sicaklik button#suz
5 button.sil[T-01] button#disa-aktar
6 input#suz-kod button#sirala-tarih
7 input#suz-uyari button#sirala-sicaklik
8 button#suz button.sil[T-01]
tersine dusen cift sayisi: 13
"a.atlama-bagi" gorselde once, sekmede "button#disa-aktar" ogesinden sonra
"input#suz-kod" gorselde once, sekmede "button#disa-aktar" ogesinden sonra
"input#suz-kod" gorselde once, sekmede "button#sirala-tarih" ogesinden sonra
"input#suz-kod" gorselde once, sekmede "button#sirala-sicaklik" ogesinden sonra
suzgec paneli kaynakta yukari alindi, tabindex kaldirildi:
no sekme sirasi gorsel sira
1 a.atlama-bagi a.atlama-bagi
2 input#suz-kod input#suz-kod
3 input#suz-uyari input#suz-uyari
4 button#suz button#suz
5 button#disa-aktar button#disa-aktar
6 button#sirala-tarih button#sirala-tarih
7 button#sirala-sicaklik button#sirala-sicaklik
8 button.sil[T-01] button.sil[T-01]
tersine dusen cift sayisi: 0
Sekiz ögelik bir sayfada on üç ters çift oluşuyor. Sayı, iki hatanın çarpımıdır: artı
tabindex değeri tek başına yedi çift üretir, kaynak sırasındaki yanlış yerleşim kalanı.
İkinci rapor düzeltmenin yönünü gösteriyor — sıra, sunum katmanında değil kaynakta
düzeltilir. Görsel sırayı sunum katmanı belirlediği için, kaynağı ona uydurmaktan başka
tutarlı bir yol yoktur.
Ters çift sayısı bir denetim ölçüsü olarak kullanılabilir: sıfırdan büyük her değer, en az bir kullanıcının ekranda geriye sıçradığı anlamına gelir. Ölçüm tablosunun her satırındaki silme düğmesi sekme sırasını uzatıyorsa, Tarayıcı ve Web Platformu kursunda kurulan dönen sekme durağı düzeni grubu tek durağa indirir; bu, sırayı bozmadan uzunluğu düşürür.
Odak Kuşatması ve Tuzak
Ölçüm giriş formu bir pencere olarak açıldığında arkasındaki sayfa erişilebilir kalmamalıdır: sekme tuşu pencerenin içinde dönmelidir. Bu düzene odak kuşatması denir ve yanlış kurulduğunda 2.1.2 ölçütünü kaldıran bir tuzağa dönüşür. Aşağıdaki betik üç kurulumu bir durum makinesi olarak modelliyor ve her birinden hangi durumların erişilebilir olduğunu tarayarak denetliyor.
// tuzak.mjs — odak kusatmasinin durum makinesi ve klavye tuzagi denetimi (2.1.2) const ARKA = ["input#suz-kod", "button#suz", "button#yeni-olcum", "button.sil[T-01]"]; const PENCERE = ["input#tarih", "input#deger", "button#kaydet", "button#kapat"]; const TUSLAR = ["Tab", "Shift+Tab", "Escape"]; // tur: "kusatmasiz" | "cikissiz" | "dogru" function makine(tur) { const cikisVar = tur !== "cikissiz"; return (d, tus) => { if (!d.acik) { const i = ARKA.indexOf(d.odak); if (tus === "Tab") return { acik: false, odak: ARKA[(i + 1) % ARKA.length] }; if (tus === "Shift+Tab") return { acik: false, odak: ARKA[(i + ARKA.length - 1) % ARKA.length] }; return d; } const halka = tur === "kusatmasiz" ? [...PENCERE, ...ARKA] : PENCERE; const i = halka.indexOf(d.odak); if (tus === "Tab") return { acik: true, odak: halka[(i + 1) % halka.length] }; if (tus === "Shift+Tab") return { acik: true, odak: halka[(i + halka.length - 1) % halka.length] }; if (tus === "Escape" && cikisVar) return { acik: false, odak: "button#yeni-olcum" }; return d; }; } const anahtar = (d) => (d.acik ? "acik" : "kapali") + "|" + d.odak; function denetle(tur) { const sonraki = makine(tur); const baslangic = { acik: true, odak: PENCERE[0] }; const gorulen = new Map([[anahtar(baslangic), baslangic]]); const kuyruk = [baslangic]; while (kuyruk.length > 0) { const d = kuyruk.shift(); for (const tus of TUSLAR) { const y = sonraki(d, tus); if (gorulen.has(anahtar(y))) continue; gorulen.set(anahtar(y), y); kuyruk.push(y); } } const durumlar = [...gorulen.values()]; return { ulasilan: durumlar.length, kacak: durumlar.some((d) => d.acik && ARKA.includes(d.odak)), cikis: durumlar.some((d) => !d.acik), }; } console.log("kurulum ulasilan durum arka plana kacak klavyeyle cikis 2.1.2"); for (const tur of ["kusatmasiz", "cikissiz", "dogru"]) { const s = denetle(tur); console.log( tur.padEnd(14) + String(s.ulasilan).padStart(14) + (s.kacak ? " var" : " yok").padEnd(18) + (s.cikis ? " var" : " yok").padEnd(17) + (s.cikis ? "gecti" : "KALDI"), ); } // Dogru kurulumda tus dizisi console.log("\ndogru kurulumda odak izi:"); let d = { acik: true, odak: PENCERE[0] }; const sonraki = makine("dogru"); console.log(" " + "baslangic".padEnd(18) + " " + "pencere acik".padEnd(14) + " odak: " + d.odak); for (const tus of ["Tab", "Tab", "Tab", "Tab", "Shift+Tab", "Escape", "Tab"]) { d = sonraki(d, tus); console.log(` ${tus.padEnd(18)} ${(d.acik ? "pencere acik" : "pencere kapali").padEnd(14)} odak: ${d.odak}`); }
kurulum ulasilan durum arka plana kacak klavyeyle cikis 2.1.2 kusatmasiz 12 var var gecti cikissiz 4 yok yok KALDI dogru 8 yok var gecti dogru kurulumda odak izi: baslangic pencere acik odak: input#tarih Tab pencere acik odak: input#deger Tab pencere acik odak: button#kaydet Tab pencere acik odak: button#kapat Tab pencere acik odak: input#tarih Shift+Tab pencere acik odak: button#kapat Escape pencere kapali odak: button#yeni-olcum Tab pencere kapali odak: button.sil[T-01]
Üç satır üç ayrı kusuru ayırıyor. Kuşatmasız kurulum ölçütü geçiyor — çıkış vardır — ama pencere açıkken arka plandaki dört durağa odak kaçıyor. Kullanıcı görmediği, üstü örtülü ögelere odaklanır; bu bir uygunluk ihlali değil, kullanımı bozan bir kusurdur. Çıkışsız kurulum yalnızca dört duruma ulaşıyor: pencerenin dışına çıkan hiçbir yol yok. Bu, tanımı gereği klavye tuzağıdır ve 2.1.2 ölçütünü kaldırır. Doğru kurulumda sekiz durumun tamamına ulaşılıyor, kaçak yok ve çıkış var.
Odak izinin son iki satırı ayrı bir kuralı gösteriyor: pencere kapandığında odak, pencereyi açan düğmeye döner. Dönmezse odak belgenin köküne düşer ve kullanıcı dolaşıma en baştan başlar. Bu davranış Tarayıcı ve Web Platformu kursunda odağı program taşıdığında doğan sorumluluk olarak kurulmuştu; burada ölçütün gereği hâline geliyor.
Kuşatmanın kendisi de tek bir yolla kurulmaz. Sekme tuşunu yakalayıp odağı halkada döndürmek bir yoldur; pencere dışındaki içeriği ağaçtan gizlemek ve etkileşim dışı bırakmak diğeridir. İkincisi yeğlenir, çünkü kuşatmayı yalnızca sekme tuşuna değil, ekran okuyucunun kendi gezinme komutlarına karşı da kurar — sekme tuşu yakalanmış bir pencerede kullanıcı yer imi listesinden arka plana geçebilir.
Odağın Görünürlüğü
Klavyeyle çalışan bir arayüzde odağın nerede olduğu her an görünmelidir; 2.4.7 ölçütü bunu ister. Odak göstergesini sunum katmanından kaldırmak, ölçütü tek satırda kaldırır. Gösterge değiştirilebilir: kalınlığı, rengi ve biçimi tasarım kararıdır. Kaldırılamaz.
İki ek koşul vardır. Gösterge, komşu renklerden ayırt edilebilecek kadar karşıtlık taşımalıdır — bu, metin dışı kontrast ölçütünün konusudur ve karşıtlık dersinde hesaplanacaktır. Odaklanan öge ayrıca ekranda örtülü kalmamalıdır: sayfanın altına yapışan bir çubuk ya da üstte duran bir başlık, odaklanan ögeyi tamamen kapatıyorsa gösterge var ama görünmez demektir.
Kısayollar
Ölçüm listesinde süzgeç alanına atlamak için tek harflik bir kısayol yazmak akla yakın gelir. 2.1.4 ölçütü bu tür kısayollara üç koşuldan en az birini dayatır: kısayol kapatılabilmeli, kullanıcı tarafından bir değiştirici tuşla birlikte kullanılacak biçimde yeniden atanabilmeli, ya da yalnızca ilgili bileşen odaktayken etkin olmalıdır.
Gerekçe iki kullanım biçiminde görünür. Sesle yazan bir kullanıcının çevresindeki konuşma metne çevrilebilir ve tek harfler kısayol olarak yorumlanır; klavyede tek tuşa basmakta zorlanan bir kullanıcı istemeden kısayol tetikler. Ölçüm giriş formunda bu, açıklama alanına yazarken satırların silinmesi anlamına gelir.
Uygulanabilir karar şudur: tek karakterli kısayollar belge geneline değil, odaktaki bileşene bağlanır. Ölçüm tablosunda ok tuşlarıyla satır gezinmesi tablo odaktayken çalışır; tablo odakta değilken aynı tuşlar sayfayı kaydırır. Belge geneline bağlanan kısayollar bir değiştirici tuşla yazılır ve kullanıcıya kapatma yolu bırakılır.
Özet
- 2.1.1 bütün işlevlerin klavye arayüzünden ve tuş vuruşu zamanlamasına bağlı olmadan çalıştırılmasını ister; 2.1.2 girilen her yerden aynı arayüzle çıkılmasını ister.
- Sekme sırası kaynaktan, görsel sıra sunum katmanından gelir; ayrışma tersine düşen çiftler üretir ve düzeltme sunum katmanında değil kaynakta yapılır.
- Artı
tabindexdeğerleri bütün sıranın önüne geçerek ters çift sayısını tek başına yükseltir. - Odak kuşatması üç kurulumda farklı sonuç verir: kuşatmasız kurulum arka plana kaçak bırakır, çıkışsız kurulum klavye tuzağıdır, doğru kurulumda kapanışta odak açan denetime döner.
- Odak göstergesi biçim olarak değiştirilebilir ama kaldırılamaz; ayrıca yeterli karşıtlık taşımalı ve başka bir ögeyle örtülmemelidir.
- Tek karakterli kısayollar kapatılabilir, yeniden atanabilir ya da yalnızca bileşen odaktayken etkin olmalıdır.
Sonraki Adım
Bu ders odağın nereye gittiğini hesapladı ama odak oraya vardığında kullanıcının ne öğrendiğini sormadı. Pencere açıldığında yeni bir bağlamın açıldığı nasıl bildirilir? Süzgeç uygulandığında tabloda kaç satır kaldığı, ekranı görmeyen bir kullanıcıya nasıl ulaşır? Sonraki ders duyuru düzeneklerini ele alır: canlı bölgelerin kibar ve atılgan ayrımını, duyuruların kuyruklanmasını ve yer imleriyle başlıkların kurduğu gezinme düzlemlerini.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.