İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat