Ders 18 / 25
İskelet Ekranlar
İskelet ekranın belirtimi; erişilebilirlik ağacındaki yeri, çok bölgeli yüklemede kümülatif düzen kaymasının oturum penceresiyle hesaplanması ve iskeletin taahhüt ettiği yapının yanlış çıkmasının maliyeti.
İçindekiler
Önceki dersin göstergesi “çalışıyorum” dedi ama gelecek içeriğin yerini tutmadı. Katalog sayfası açıldığında ekranda beliren şey tek bir bölüm değildir: üst şerit, süzgeç paneli, sonuç listesi ve öneriler ayrı zamanlarda gelir. Her biri geldiğinde altındaki her şey aşağı itilir.
İskelet ekran (skeleton screen), gelecek içeriğin yerini önceden tutan gri kutulardan oluşan yükleme görünümüdür. Yükleme ve Boş Durumlar dersinde tek bir kararsız öge üzerinden ölçülmüş ve iskeletin satır sayısı görünür alana göre belirlenmişti. Bu ders aynı kalıbı belirtim olarak yazar ve ölçümü iki yönde derinleştirir: iskeletin erişilebilirlik ağacında ne olduğu, ve birden çok bölge ayrı zamanlarda geldiğinde kaymanın nasıl biriktiği.
Ne Çözer, Ne Zaman Kullanılmaz
İskelet iki iş yapar. Yer ayırarak yerleşim kaymasını engeller, ve gelecek içeriğin yapısını önceden göstererek beklemenin daha kısa algılanmasını sağlar. İkinci iş bir algı etkisidir ve ölçülmesi zordur; birinci iş sayıya çevrilebilir ve kararın dayanağı odur.
Kullanılmayacak durumlar üç tanedir:
- Sonuç gelmeme olasılığı yüksekse. İskelet dokuz satır çizip üç sonuç geldiğinde kullanıcı bir an dokuz sonuç olduğunu sanır; hiç sonuç gelmezse iskelet düpedüz yalan söylemiş olur.
- Yapı bilinmiyorsa. İskelet, çizdiği kutuların yerini taahhüt eder. Gelecek içeriğin satır yüksekliği ya da alan sayısı değişkense taahhüt tutmaz ve kayma yine oluşur.
- Bekleme algı eşiğinin altındaysa. Yüz milisaniyede gelen bir içerik için çizilen iskelet, kendisi bir titreme kaynağıdır.
Yerel Öge Önce, ARIA Sonra
İskeletin işaretlemede karşılığı yoktur; kutular yalnız görsel yer tutuculardır. Belirtimin kritik yanı da budur: iskelet ögeleri erişilebilirlik ağacında bulunmamalıdır.
| Parça | Kaynak |
|---|---|
| İskelet kutuları | aria-hidden="true" — anlam taşımayan süsleyici ögeler |
| Yenilenen bölüm | aria-busy="true", içerik geldiğinde kaldırılır |
| Durum bildirimi | Ayrı bir kibar bölge: “Sonuçlar yükleniyor” -> “12 kayıt listeleniyor” |
| Odak | Bölümün kendisi değiştirilmez; içindeki odak korunur |
Gerekçe doğrudandır. İskelet kutuları ağaçta göründüğünde ekranda okunacak bir şey varmış gibi bildirilir; oysa hiçbiri metin taşımaz. Simge Kullanımı dersindeki süsleyici öge kuralı burada tam olarak geçerlidir: anlam taşımayan görsel gizlenir.
Bekleme bilgisi bu yüzden iskeletten değil, durum bölgesinden gelir. Bölge sayfa kurulurken konur, yükleme başladığında kısa bir cümle alır ve içerik geldiğinde sonuç sayısıyla güncellenir. Bildirim Şeritleri dersindeki iki kural burada da geçerlidir: bölge önceden bulunmalı ve son durumu bildirmelidir.
Kayma Tek Bir Olay Değil, Bir Toplamdır
Yerleşim kayması puanı Yükleme ve Boş Durumlar dersinde tanımlanmıştı: kayan içeriğin etkilediği alanın görüntü alanına oranı ile kat edilen uzaklığın en büyük kenara oranının çarpımı. Frontend Kalitesi kursunda bu puanın oturum penceresi içinde toplandığı eklenmişti: bir pencere ilk kaymayla başlar, bir saniyeden uzun bir boşlukta ya da beş saniye dolduğunda kapanır; kümülatif düzen kayması pencerelerin en büyüğüdür.
Aşağıdaki betik dört bölgeli bir katalog sayfasını dört ayrı iskelet kurulumuyla yüklüyor ve her kurulumun kaymalarını pencerelere ayırıyor.
// iskelet.mjs — cok bolgeli yuklemede kumulatif duzen kaymasi ve oturum penceresi const GORUNTU = { g: 1280, y: 800 }; const ALTLIK = 80; // Sayfa, gorsel sirada bolgelerden olusur. Her bolge kendi aninda gercek yuksekligine gecer. const TASARIM = [ { ad: "uyari seridi", an: 6500, gercek: 64, iskelet: 0 }, { ad: "ust serit", an: 400, gercek: 96, iskelet: 96 }, { ad: "suzgec paneli", an: 700, gercek: 120, iskelet: 120 }, { ad: "sonuc listesi", an: 1500, gercek: 648, iskelet: 648 }, // 9 satir x 72 px { ad: "oneriler", an: 2200, gercek: 240, iskelet: 240 }, ]; // Senaryolar: her bolgenin baslangic (yer tutucu) yuksekligi ve gercek yuksekligi. const SENARYO = { "iskeletsiz": (b) => ({ bas: b.ad === "sonuc listesi" ? 120 : 0, son: b.gercek }), "kismi iskelet": (b) => ({ bas: b.ad === "sonuc listesi" ? b.iskelet : 0, son: b.gercek }), "tam iskelet": (b) => ({ bas: b.iskelet, son: b.gercek }), "iskelet 9 satir, 3 sonuc": (b) => ({ bas: b.iskelet, son: b.ad === "sonuc listesi" ? 3 * 72 : b.gercek, }), }; function kaymalar(senaryo) { const kur = SENARYO[senaryo]; const yukseklik = TASARIM.map((b) => kur(b).bas); const olaylar = TASARIM.map((b, i) => ({ i, an: b.an, ad: b.ad, son: kur(b).son })) .sort((a, b) => a.an - b.an); const sonuc = []; for (const o of olaylar) { const delta = o.son - yukseklik[o.i]; if (delta === 0) { yukseklik[o.i] = o.son; continue; } // Degisen bolgenin altinda kalan icerigin ust kenari ve toplam yuksekligi const ustKenar = yukseklik.slice(0, o.i).reduce((a, b) => a + b, 0); const oncekiAltUst = ustKenar + yukseklik[o.i]; const altToplam = yukseklik.slice(o.i + 1).reduce((a, b) => a + b, 0) + ALTLIK; const sonrakiAltUst = oncekiAltUst + delta; // Etki payi: kayan icerigin once ve sonraki gorunur alanlarinin birlesimi const gorunur = (ust) => [Math.max(0, Math.min(GORUNTU.y, ust)), Math.max(0, Math.min(GORUNTU.y, ust + altToplam))]; const [o1, o2] = gorunur(oncekiAltUst); const [s1, s2] = gorunur(sonrakiAltUst); const etkiPayi = (Math.max(o2, s2) - Math.min(o1, s1)) / GORUNTU.y; const uzaklikPayi = Math.abs(delta) / Math.max(GORUNTU.g, GORUNTU.y); sonuc.push({ an: o.an, ad: o.ad, delta, etkiPayi, uzaklikPayi, puan: etkiPayi * uzaklikPayi }); yukseklik[o.i] = o.son; } return sonuc; } // Oturum penceresi: ilk kaymayla baslar, 1 sn'den uzun bosluk ya da 5 sn'lik sure kapatir. function pencereler(kaymalar) { const p = []; for (const k of kaymalar) { const son = p[p.length - 1]; if (son && k.an - son.sonAn <= 1000 && k.an - son.basAn <= 5000) { son.sonAn = k.an; son.toplam += k.puan; son.sayi++; } else p.push({ basAn: k.an, sonAn: k.an, toplam: k.puan, sayi: 1 }); } return p; } let ilk = true; for (const ad of Object.keys(SENARYO)) { const k = kaymalar(ad); if (!ilk) console.log(""); ilk = false; console.log(ad); console.log(" an bolge delta etki payi uzaklik payi kayma puani"); for (const s of k) console.log( ` ${String(s.an).padStart(4)} ${s.ad.padEnd(14)} ${((s.delta > 0 ? "+" : "") + s.delta + " px").padStart(7)} ` + `${s.etkiPayi.toFixed(4).padStart(10)} ${s.uzaklikPayi.toFixed(4).padStart(13)} ${s.puan.toFixed(4).padStart(12)}`, ); const p = pencereler(k); for (const w of p) console.log(` oturum penceresi ${w.basAn}-${w.sonAn} ms: ${w.sayi} kayma, toplam ${w.toplam.toFixed(4)}`); const cls = p.length ? Math.max(...p.map((w) => w.toplam)) : 0; console.log(` kumulatif duzen kaymasi: ${cls.toFixed(4)} (iyi kabul edilen ust sinir 0.100) -> ${cls <= 0.1 ? "gecer" : "KALIR"}`); }
iskeletsiz
an bolge delta etki payi uzaklik payi kayma puani
400 ust serit +96 px 0.3700 0.0750 0.0278
700 suzgec paneli +120 px 0.4000 0.0938 0.0375
1500 sonuc listesi +528 px 0.5800 0.4125 0.2392
2200 oneriler +240 px 0.0000 0.1875 0.0000
6500 uyari seridi +64 px 1.0000 0.0500 0.0500
oturum penceresi 400-2200 ms: 4 kayma, toplam 0.3045
oturum penceresi 6500-6500 ms: 1 kayma, toplam 0.0500
kumulatif duzen kaymasi: 0.3045 (iyi kabul edilen ust sinir 0.100) -> KALIR
kismi iskelet
an bolge delta etki payi uzaklik payi kayma puani
400 ust serit +96 px 1.0000 0.0750 0.0750
700 suzgec paneli +120 px 0.8800 0.0938 0.0825
2200 oneriler +240 px 0.0000 0.1875 0.0000
6500 uyari seridi +64 px 1.0000 0.0500 0.0500
oturum penceresi 400-700 ms: 2 kayma, toplam 0.1575
oturum penceresi 2200-2200 ms: 1 kayma, toplam 0.0000
oturum penceresi 6500-6500 ms: 1 kayma, toplam 0.0500
kumulatif duzen kaymasi: 0.1575 (iyi kabul edilen ust sinir 0.100) -> KALIR
tam iskelet
an bolge delta etki payi uzaklik payi kayma puani
6500 uyari seridi +64 px 1.0000 0.0500 0.0500
oturum penceresi 6500-6500 ms: 1 kayma, toplam 0.0500
kumulatif duzen kaymasi: 0.0500 (iyi kabul edilen ust sinir 0.100) -> gecer
iskelet 9 satir, 3 sonuc
an bolge delta etki payi uzaklik payi kayma puani
1500 sonuc listesi -432 px 0.4600 0.3375 0.1553
6500 uyari seridi +64 px 1.0000 0.0500 0.0500
oturum penceresi 1500-1500 ms: 1 kayma, toplam 0.1553
oturum penceresi 6500-6500 ms: 1 kayma, toplam 0.0500
kumulatif duzen kaymasi: 0.1553 (iyi kabul edilen ust sinir 0.100) -> KALIR
Dört kurulum dört ayrı sonuç veriyor ve aralarındaki fark tek bir sayıya inmiyor.
İskeletsiz kurulum 0.3045 puanla eşiğin üç katını üretiyor. Beklendiği gibi en büyük pay sonuç listesindedir: 528 pikselin bir anda araya girmesi tek başına 0.2392 puan demektir.
Kısmi iskelet toplamı 0.1575’e indiriyor ama beklenenden az. Nedeni tablonun ilk iki satırındadır: yalnız listeye iskelet konduğunda üst şeridin ve süzgeç panelinin kaymaları büyüyor — etki payı 0.3700’den 1.0000’e, 0.4000’den 0.8800’e çıkıyor. Sebep açıktır: artık onların altında itilecek uzun bir iskelet blok vardır. Bir bölgeye iskelet koymak, üstündeki bölgelerin kaymalarını ucuzlatmaz, pahalılaştırır. İskelet kısmi uygulandığında kazanç da kısmi kalır.
Tam iskelet ilk penceredeki bütün kaymaları sıfırlıyor. Geriye yalnız altıncı saniyede gelen uyarı şeridi kalıyor; o da ayrı bir oturum penceresine düştüğü için toplama eklenmiyor ve kümülatif değer 0.0500’de kalıyor. Pencerelerin ayrı sayılması burada belirleyicidir: aynı beş kayma tek bir pencereye düşseydi sonuç eşiği aşardı.
Yanlış taahhüt senaryosu iskeletin kendi riskini ölçüyor. Dokuz satırlık iskelet çizilip üç sonuç geldiğinde liste 432 piksel kısalıyor ve 0.1553 puan üretiyor. Kayma yönü önemsizdir; içeriğin yukarı sıçraması da aşağı itilmesi kadar puan üretir. Buradan kesin bir kural çıkar: iskeletin satır sayısı, sonuç sayısı bilindiğinde ona eşitlenir; bilinmiyorsa iskelet, kaymayı taşıyabilecek kadar küçük seçilir.
Öneriler bölgesi bütün senaryolarda sıfır puan üretiyor. Nedeni, o bölgenin altındaki içeriğin görüntü alanının dışında kalmasıdır; ekranda görünmeyen bir kayma ölçüte girmez. Bu, alt bölgelerin iskeletsiz bırakılabileceği anlamına gelmez — küçük ekranda aynı bölge görünür alana girer ve puan üretir.
Ölçülebilir Kısıtlar
Kümülatif düzen kayması 0.100. İyi kabul edilen üst sınır budur ve hesap yukarıdaki gibi oturum pencereleriyle yapılır. Bu bir başarı ölçütü değil bir başarım eşiğidir, ama erişilebilirlik sonucu doğrudandır: kayan bir düzen, düğmeye basmak üzere olan kullanıcının yanlış düğmeye basmasına yol açar.
2.2.2 Duraklat, Durdur, Gizle. İskelet kutularının üzerinden geçen parıltı hareketi beş saniyeden uzun sürüyorsa durdurulabilmelidir. Hareketi azaltma tercihi bildirilmişse parıltı tümüyle kaldırılır; iskelet düz gri kutular olarak da işini yapar.
2.3.1 Üç Yanıp Sönme. Parıltının saniyede üçten fazla yinelenmemesi gerekir.
1.4.11 Metin Dışı Kontrast iskelet kutularına uygulanmaz, çünkü kutular anlam taşımaz ve ağaçtan gizlidir. Buna karşılık iskeletin yüzeyle karışacak kadar silik olması kullanıcının sayfayı boş sanmasına yol açar; bu bir ölçüt değil bir tasarım kararıdır.
4.1.3 Durum İletileri. Yükleme ve tamamlanma bildirimleri iskeletten değil kibar durum bölgesinden gelir.
Sık Yapılan Hata ve Nasıl Anlaşılır
İskelet ağaçta görünüyor. Kutular gizlenmediğinde ekran boş sayılmaz, ama okunacak hiçbir şey de bulunmaz. Anlaşılması: yükleme sırasında sayfadaki metin ögelerini saymak; iskelet kutuları sayıya giriyorsa gizleme eksiktir.
İskelet yalnız bir bölgeye konmuş. Kazanç beklenenden az çıkar ve üstteki bölgelerin kaymaları büyür. Anlaşılması: kaymaları bölge bölge ölçmek; iskeletli bölgenin üstündeki puanlar artmışsa neden budur.
İskelet gerçek satır yüksekliğini taklit etmiyor. Kutular gerçek satırdan kısa çizildiğinde içerik geldiğinde yine kayma olur. Anlaşılması: iskelet yüksekliği ile yerleşmiş satır yüksekliğini karşılaştırmak.
Geç gelen şerit sayfanın üstüne ekleniyor. Uyarı şeridi altıncı saniyede tepeye girdiğinde bütün sayfa aşağı kayar. Anlaşılması: geç gelen bölümlerin yerinin baştan ayrılıp ayrılmadığına bakmak; ayrılmıyorsa şerit içeriğin üstüne değil, akışın dışına konmalıdır.
Özet
- İskelet ekran gelecek içeriğin yerini tutar; ölçülebilir işi yerleşim kaymasını engellemektir, algılanan hıza etkisi ikincil ve doğrudan ölçülemeyen bir kazançtır.
- İskelet kutuları anlam taşımaz ve erişilebilirlik ağacından gizlenir; bekleme bilgisi iskeletten değil ayrı bir kibar durum bölgesinden gelir.
- Kayma tek bir olay değil bir toplamdır: oturum pencereleri içinde toplanır ve kümülatif değer pencerelerin en büyüğüdür; geç gelen bir kayma ayrı pencereye düşerse toplamı değiştirmez.
- Kısmi iskelet, üstündeki bölgelerin kaymalarını büyütür; ölçümde üst şeridin etki payı 0.3700’den 1.0000’e çıkar. Kazanç ancak bütün bölgeler yer ayırdığında tamamlanır.
- İskeletin çizdiği satır sayısı bir taahhüttür; dokuz satır çizip üç sonuç getirmek 0.1553 puanlık bir kayma üretir ve kaymanın yönü puanı etkilemez.
- Parıltı hareketi 2.2.2 ve 2.3.1 ölçütlerine bağlıdır; hareketi azaltma tercihinde iskelet parıltısız da işini görür.
Sonraki Adım
İskelet, içeriğin geleceği varsayımına dayanıyordu. Yükleme bittiğinde bu varsayımın tutmadığı iki durum kalır: hiçbir kayıt gelmemesi ve isteğin başarısız olması. İkisi ekranda benzer görünür —boş bir alan ve bir cümle— ama kullanıcıya bambaşka şeyler söyler ve bambaşka kurtarma yolları gerektirir. Sonraki ders bu iki durumu belirtim olarak yazar: hangi rolün ve hangi önceliğin kullanılacağını, odağın nereye gideceğini, hata özetinin alanlara nasıl bağlanacağını ve kurtarma yolunun uzunluğunu tuş sayısıyla ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.