Ders 11 / 25
Kırıntı Yolu ve Sayfalama
Hiyerarşideki ve sıradaki konumun işaretlemede bildirilmesi, bağlantı adının hedefi taşıması, bulunulan sayfanın bağlantı olarak korunması ve sayfa değişiminde odak kararı.
İçindekiler
Önceki dersteki kapsayıcılar içeriği gösterip gizledi; hiçbiri kullanıcıya nerede olduğunu söylemedi. Katalogda konum bir bilgidir: kullanıcı üç düzey aşağıda bir konu dalındadır ve sonuç listesinin dördüncü sayfasındadır. Bu iki bilgiyi taşıyan bileşenler kırıntı yolu ile sayfalamadır ve ikisi de aynı işi yapar — bulunulan yeri diğerlerinden ayırmak.
İkisinin ortak zorluğu da aynıdır. Görsel olarak bulunulan yer bir renk ya da kalınlık farkıyla belirtilir; bu fark ekranı görmeyen kullanıcıya hiçbir şey bildirmez. Ayrım işaretlemede yazılmadığında bileşen, benzer bağlantılardan oluşan bir listeye dönüşür.
Kalıbın Çözdüğü Sorun
Kırıntı yolu hiyerarşideki konumu, sayfalama sıradaki konumu bildirir. Kırıntı yolu aynı zamanda bir yukarı çıkış yoludur: kullanıcı üç düzey yukarıya tek adımda döner. Sayfalama ise bir kümenin parçalara bölündüğünü ve hangi parçanın görüldüğünü söyler.
Hiçbiri gezinmenin tek yolu değildir. Kırıntı yolu ana gezinmenin yerine geçmez; sayfalama sonsuz kaydırmanın yerine geçmez. İkisi de bir ek bilgi katmanıdır ve bu yüzden ikisi de kendi gezinme bölgesinde durur.
Yerel Öge Önce
Her iki bileşen de yerel ögelerle kurulur: bir gezinme bölgesi, içinde bir liste, listede bağlantılar. Kırıntı yolunda liste sıralıdır, çünkü öğelerin sırası anlam taşır. Sayfalamada sıra da anlamlıdır ama öğeler eşit düzeydedir.
Sayfada birden çok gezinme bölgesi varsa her birinin adı olmalıdır; adsız iki bölge
birbirinden ayrılamaz. Ad, bölgenin ne için olduğunu söyler: “Kırıntı yolu”, “Sonuç
sayfaları”. Bulunulan yer ise aria-current ile bildirilir — kırıntı yolunda son öge,
sayfalamada bulunulan sayfa numarası.
Aşağıdaki hesap iki bileşeni de küçük bir belge modeli üzerinde kurar ve erişilebilir adları hesaplar.
// konum.mjs — kirinti yolu ve sayfalama: erisilebilir ad, bulunulan yer bildirimi ve odak // Kucuk bir belge modeli: dugum agaci ve erisilebilir ad hesabi. const dugum = (etiket, ozn = {}, cocuk = []) => ({ etiket, ozn, cocuk }); const metin = (d) => d.etiket === "#metin" ? d.ozn.deger : d.cocuk.map(metin).join("").replace(/\s+/g, " ").trim(); const yaz = (s) => dugum("#metin", { deger: s }); // Erisilebilir ad onceligi: aria-label > aria-labelledby > icerik metni. function erisilebilirAd(d, belge) { if (d.ozn["aria-label"]) return d.ozn["aria-label"]; if (d.ozn["aria-labelledby"]) { const hedef = bul(belge, d.ozn["aria-labelledby"]); return hedef ? metin(hedef) : ""; } return metin(d); } function bul(d, kimlik) { if (d.ozn.id === kimlik) return d; for (const c of d.cocuk) { const s = bul(c, kimlik); if (s) return s; } return null; } const gez = function* (d) { yield d; for (const c of d.cocuk) yield* gez(c); }; // 1. Kirinti yolu: gezinme bolgesi + siralı liste + bulunulan sayfa isareti. const kirinti = dugum("nav", { "aria-label": "Kırıntı yolu" }, [ dugum("ol", {}, [ dugum("li", {}, [dugum("a", { href: "/katalog" }, [yaz("Katalog")])]), dugum("li", {}, [dugum("a", { href: "/katalog/bilim" }, [yaz("Bilim")])]), dugum("li", {}, [dugum("a", { href: "/katalog/bilim/gokbilim" }, [yaz("Gökbilim")])]), dugum("li", {}, [dugum("span", { "aria-current": "page" }, [yaz("Kuzey Yamaç Gözlem Kayıtları")])]), ]), ]); // 2. Sayfalama: gezinme bolgesi + baglantilar + bulunulan sayfa. const sayfalama = dugum("nav", { "aria-label": "Sonuç sayfaları" }, [ dugum("ul", {}, [ dugum("li", {}, [dugum("a", { href: "?sayfa=3", "aria-label": "Önceki sayfa" }, [yaz("‹")])]), dugum("li", {}, [dugum("a", { href: "?sayfa=1" }, [yaz("1")])]), dugum("li", {}, [dugum("span", { "aria-hidden": "true" }, [yaz("…")])]), dugum("li", {}, [dugum("a", { href: "?sayfa=3" }, [yaz("3")])]), dugum("li", {}, [dugum("a", { href: "?sayfa=4", "aria-current": "page" }, [yaz("4")])]), dugum("li", {}, [dugum("a", { href: "?sayfa=5" }, [yaz("5")])]), dugum("li", {}, [dugum("a", { href: "?sayfa=5", "aria-label": "Sonraki sayfa" }, [yaz("›")])]), ]), ]); function denetle(ad, kok) { console.log(`\n--- ${ad} ---`); console.log(`bolge adi : ${erisilebilirAd(kok, kok) || "(yok)"}`); const bulunulan = [...gez(kok)].filter((d) => d.ozn["aria-current"] === "page"); console.log(`bulunulan yer isareti: ${bulunulan.length} oge` + (bulunulan.length === 1 ? ` ("${metin(bulunulan[0])}")` : "")); const baglantilar = [...gez(kok)].filter((d) => d.etiket === "a"); console.log("baglanti adlari :"); for (const b of baglantilar) { const ad2 = erisilebilirAd(b, kok); const uyari = ad2.length <= 2 && !b.ozn["aria-label"] ? " <- ad tek karakter, hedefi bildirmiyor" : ""; console.log(` ${(b.ozn.href || "").padEnd(24)} ad: ${JSON.stringify(ad2).padEnd(22)}${uyari}`); } const gizli = [...gez(kok)].filter((d) => d.ozn["aria-hidden"] === "true").length; console.log(`agactan gizlenen susleyici oge: ${gizli}`); return { bulunulan: bulunulan.length, baglanti: baglantilar.length }; } denetle("kirinti yolu", kirinti); denetle("sayfalama", sayfalama); // 3. Sayfa degisiminde odak: uc secenek ve sonuclari. // Model: klavye kullanicisi 4. sayfa baglantisina basiyor, liste yeniden ciziliyor. const SECENEKLER = [ { ad: "odak dokunulmuyor", odak: "basilan baglanti", kayip: false, duyuru: "yok" }, { ad: "odak listenin basina", odak: "sonuc listesi kabi", kayip: false, duyuru: "liste basligi" }, { ad: "odak sayfa basina", odak: "belge koku", kayip: true, duyuru: "sayfa basligi" }, ]; console.log("\n--- sayfa degisiminde odak karari ---"); console.log("secenek odagin gittigi yer yer kaybi duyuru"); for (const s of SECENEKLER) { console.log( s.ad.padEnd(24) + s.odak.padEnd(25) + (s.kayip ? "var" : "yok").padEnd(11) + s.duyuru ); } // 4. Bulunulan sayfa baglanti olarak birakilirsa ne olur? // Sekme sirasinda kac oge var ve kacinin hedefi ayni sayfa? function sekmeSirasi(kok) { return [...gez(kok)].filter((d) => d.etiket === "a" && d.ozn.href !== undefined); } const s1 = sekmeSirasi(sayfalama); const ayniHedef = s1.filter((d) => d.ozn["aria-current"] === "page").length; console.log(`\nsayfalamada sekme duragi: ${s1.length}`); console.log(`bulunulan sayfayi da baglanti birakan durak: ${ayniHedef}`); console.log(` -> bulunulan sayfa baglanti kalirsa kullanici ayni sayfaya gider; ` + `isaret bildirimi korunur, hedef degismez.`); // 5. Kirinti yolunun uzunlugu: derinlik arttikca ad uzunlugu ve kirpma kurali. const KIRP = 18; console.log("\n--- kirinti ogelerinin kirpilmasi ---"); for (const d of [...gez(kirinti)].filter((x) => ["a", "span"].includes(x.etiket))) { const tam = metin(d); const gorunen = tam.length > KIRP ? tam.slice(0, KIRP - 1) + "…" : tam; console.log( ` gorunen: ${JSON.stringify(gorunen).padEnd(32)} erisilebilir ad: ${JSON.stringify(tam)}` + (gorunen !== tam ? " <- gorunen ad kisaltildi, erisilebilir ad tam kalmali" : "") ); }
--- kirinti yolu ---
bolge adi : Kırıntı yolu
bulunulan yer isareti: 1 oge ("Kuzey Yamaç Gözlem Kayıtları")
baglanti adlari :
/katalog ad: "Katalog"
/katalog/bilim ad: "Bilim"
/katalog/bilim/gokbilim ad: "Gökbilim"
agactan gizlenen susleyici oge: 0
--- sayfalama ---
bolge adi : Sonuç sayfaları
bulunulan yer isareti: 1 oge ("4")
baglanti adlari :
?sayfa=3 ad: "Önceki sayfa"
?sayfa=1 ad: "1" <- ad tek karakter, hedefi bildirmiyor
?sayfa=3 ad: "3" <- ad tek karakter, hedefi bildirmiyor
?sayfa=4 ad: "4" <- ad tek karakter, hedefi bildirmiyor
?sayfa=5 ad: "5" <- ad tek karakter, hedefi bildirmiyor
?sayfa=5 ad: "Sonraki sayfa"
agactan gizlenen susleyici oge: 1
--- sayfa degisiminde odak karari ---
secenek odagin gittigi yer yer kaybi duyuru
odak dokunulmuyor basilan baglanti yok yok
odak listenin basina sonuc listesi kabi yok liste basligi
odak sayfa basina belge koku var sayfa basligi
sayfalamada sekme duragi: 6
bulunulan sayfayi da baglanti birakan durak: 1
-> bulunulan sayfa baglanti kalirsa kullanici ayni sayfaya gider; isaret bildirimi korunur, hedef degismez.
--- kirinti ogelerinin kirpilmasi ---
gorunen: "Katalog" erisilebilir ad: "Katalog"
gorunen: "Bilim" erisilebilir ad: "Bilim"
gorunen: "Gökbilim" erisilebilir ad: "Gökbilim"
gorunen: "Kuzey Yamaç Gözle…" erisilebilir ad: "Kuzey Yamaç Gözlem Kayıtları" <- gorunen ad kisaltildi, erisilebilir ad tam kalmali
Adın Hedefi Bildirmesi
Çıktının ikinci bölümü sayfalamanın en sık kusurunu gösteriyor: bağlantıların erişilebilir
adı tek karakterdir. “3” adlı bir bağlantı, bağlantı listesini gezen bir kullanıcıya hiçbir
şey söylemez. Ok işaretleri için aria-label yazılmışken sayı bağlantıları için
yazılmamıştır; oysa sorun ikisinde de aynıdır.
Çözüm, görünen metni değiştirmeden erişilebilir adı tamamlamaktır: bağlantının adı “3” değil “Sayfa 3” olur. Görsel sunum sayıyı gösterir, ad sayının ne olduğunu söyler. Aynı kural süsleyici öge için tersine işler: sayfalamadaki üç nokta bir bilgi taşımaz ve ağaçtan gizlenir; çıktının ikinci bölümünde gizlenen öge sayısı bunu doğruluyor.
Bulunulan Sayfa Bağlantı Kalmalı mı
Üçüncü ve dördüncü bölüm bir tasarım kararını ölçüyor. Bulunulan sayfa bağlantı olarak bırakılırsa sekme sırasında bir durak daha bulunur ve o durak kullanıcıyı aynı sayfaya götürür. Bırakılmazsa durak sayısı azalır ama bulunulan sayfanın konumu klavye sırasında görünmez olur.
Karar ölçüte bağlanabilir: bulunulan sayfa bağlantı kalır ve aria-current ile
işaretlenir. Böylece hem işaret bildirimi korunur hem de sıradaki boşluk oluşmaz; hedefin
aynı sayfa olması bir kayıp değildir, çünkü kullanıcı zaten oradadır ve işaret bunu söyler.
Sayfa Değiştiğinde Odak Nereye Gider
Üçüncü bölümdeki tablo üç seçeneği karşılaştırıyor. Odağa hiç dokunmamak en az müdahaledir ama yeni içeriğin geldiğini hiçbir şey bildirmez. Odağı sayfanın başına almak yer kaybına yol açar: kullanıcı sayfalama denetimine dönmek için baştan aşağı iner.
Üçüncüsü — odağı sonuç listesinin kabına almak — iki sorunu da çözer: kap odaklanabilir yapılır, başlığı okunur ve kullanıcı yeni içeriğin başındadır. Sayfalama denetimi listenin hemen altında olduğu için bir sonraki sayfaya geçmek yine kısa bir yoldur.
Kırıntıda Uzun Ad
Son bölüm görsel kırpmanın kuralını gösteriyor. Uzun bir kayıt adı kırıntı yolunda kısaltılabilir; kısaltma görsel bir karardır ve erişilebilir adı etkilememelidir. Görünen metin kısaltıldığında tam ad erişilebilir ad olarak korunur; aksi hâlde kullanıcı kaydın adını asla öğrenemez.
Klavye Sözleşmesi
| Tuş | Davranış |
|---|---|
Tab |
Bir sonraki bağlantıya geçer; iki bileşen de bağlantı listesidir, ek tuş kuralı yoktur |
Shift+Tab |
Bir önceki bağlantıya döner |
Enter |
Bağlantıyı izler |
Bu bileşenlerde ok tuşu kuralı yoktur. Sekmeler ve menülerde ok tuşu gerekliydi, çünkü onlar tek bir bileşik denetimdi; kırıntı yolu ve sayfalama ise bağımsız bağlantıların listesidir ve bağlantı listesinde beklenen davranış sekmedir.
Ölçülebilir Kısıtlar
- Sayfalama bağlantılarının hedef boyutu, dokunmalı girdide en az 24×24 piksel olmalıdır (WCAG 2.5.8); aralarındaki boşluk bu ölçüyü sağlamanın ikinci yoludur.
- Bulunulan sayfa yalnız renkle ayrılamaz (WCAG 1.4.1): renge kalınlık, çerçeve ya da
başka bir kanal eşlik eder ve
aria-currentbildirimi zaten metinsel kanalı sağlar. - Kırıntı ayıracı (
/,>) süsleyicidir ve erişilebilirlik ağacından gizlenir; ayıraç metnin parçası hâline gelirse ad kirlenir. - Her gezinme bölgesinin erişilebilir adı olmalıdır (WCAG 1.3.1 kapsamında yapısal bilgi).
Sık Yapılan Hata
En sık hata, bulunulan sayfayı yalnız görsel olarak işaretlemektir. İkincisi, sayfalamayı düğmelerle kurup adres çubuğunu güncellememektir: kullanıcı dördüncü sayfayı paylaşamaz, geri tuşu bir önceki sayfaya dönmez. Sayfa numarası bir konum bilgisidir ve konum adreste taşınır — bu, Rota Parametreleri ve Sorgu dersinde kurulan kuralın arayüz tarafındaki karşılığıdır.
Özet
- Kırıntı yolu hiyerarşideki, sayfalama sıradaki konumu bildirir; ikisi de kendi adlandırılmış
gezinme bölgesinde durur ve bulunulan yeri
aria-currentile işaretler. - Erişilebilir ad hedefi bildirmelidir: “3” değil “Sayfa 3”; süsleyici ayıraç ve üç nokta ağaçtan gizlenir.
- Bulunulan sayfa bağlantı olarak kalır ve işaretlenir; sıradan çıkarmak konumu klavye kullanıcısına görünmez yapar.
- Sayfa değiştiğinde odak sonuç listesinin kabına alınır: yer kaybı olmaz, yeni içeriğin geldiği duyurulur.
- Görsel kırpma erişilebilir adı kısaltmaz; tam ad ağaçta korunur.
Sonraki Adım
Sonuç listesi bu dersle konumunu bildirir hâle geldi, ama listenin kendisi hâlâ basit bir kayıt dizisidir. Katalogda aynı veri bir tabloya dönüştüğünde — sütunlar, sıralama, süzgeçler ve yüzlerce satır — konum bilgisi iki eksene çıkar: kullanıcı hem hangi satırda hem hangi sütundadır. Sonraki ders tabloyu belirtir: başlık hücrelerinin satır ve sütunla ilişkisini, sıralama durumunun nasıl duyurulacağını ve yoğun veride hangi kısayolların gerektiğini ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.