İçeriğe geç
academia.sh

Ders 17 / 25

Yükleme Göstergeleri

Belirsiz ve belirli göstergenin belirtimi, ilerleme değerinin ve kalan süre kestiriminin hesaplanması, kestirimin kararsızlığının ölçülmesi ve ilerleme duyurularının sıklık politikası.

İçindekiler

Önceki ders bir işin bittiğini bildiren katmanı kurdu. İşin sürdüğü aralıkta ekranda ne olduğu ayrı bir kalıptır ve iki temel biçime ayrılır.

Belirsiz gösterge (indeterminate indicator) yalnız bir şey söyler: bir iş sürüyor. Tamamlanma oranı bilinmediğinde kullanılır. Belirli gösterge (determinate indicator) ise oranı da söyler; hesaplanabilir bir bütünün ne kadarının bittiğini bildirir. Aradaki fark bir görsel biçim farkı değildir: rolleri, taşıdıkları değerler, duyuruları ve güncelleme sıklıkları farklıdır. Bu ders ikisini belirtim olarak yazar ve belirli göstergenin en çok yanlış kurulan parçasını —kalan süre kestirimini— ölçer.

Ne Çözer, Ne Zaman Kullanılmaz

Gösterge iki soruyu yanıtlar: sistem çalışıyor mu, ve ne kadar kaldı? Birinci soru her beklemede vardır; ikincisi yalnız bütünün bilindiği işlerde yanıtlanabilir.

Kullanılmayacak durumlar ölçülmüş sürelere bağlıdır. Yükleme ve Boş Durumlar dersinde katalog aramasının yanıt süreleri ölçülmüş ve isteklerin çoğunun bir saniyenin altında tamamlandığı görülmüştü; o aralıkta gösterge açmak titreme üretir. Karar oradaki gecikme ve en az görünme kurallarına bağlanır; bu ders o karardan sonrasını yazar.

İki kural daha vardır. Bütün bilinmiyorsa belirli gösterge kurulmaz; yüzdeyi uydurmak, kullanıcıya yanlış bir bitiş vaat eder. Gelecek içeriğin yerini tutmak gerekiyorsa gösterge yetmez; küçük bir gösterge içerik geldiğinde yerleşim kaymasını engellemez ve o iş sonraki dersin kalıbına aittir.

Yerel Öge Önce, ARIA Sonra

İşaretlemede bu iş için ayrılmış bir öge vardır: progress. Değer verildiğinde belirli, değer verilmediğinde belirsiz gösterge olur; rolü ve değer alanlarını kendisi taşır.

Elle kurulumda alanlar tek tek verilir:

Parça Belirli gösterge Belirsiz gösterge
Rol role="progressbar" role="progressbar"
Değer aria-valuenow verilmez
Sınırlar aria-valuemin, aria-valuemax verilmez
Okunabilir değer aria-valuetext (örneğin “24 MB’ın 8 MB’ı”) verilmez
Ad aria-labelledby ya da aria-label aynı
Bölgenin durumu Yenilenen bölümde aria-busy="true" aynı

İki ayrım kritiktir. Birincisi, aria-valuenow alanının bulunmaması belirsizliğin bildirilme yoludur; sıfır yazmak “hiç ilerlemedi” demektir ve yanlıştır. İkincisi, aria-valuetext yalnız ham sayı anlamsız olduğunda yazılır: yüzde anlaşılırken “yüzde 40” metnini ayrıca yazmak duyuruyu iki katına çıkarır.

Göstergenin kendisi bir canlı bölge değildir. İlerleme değerinin her değişimini duyurmak, aşağıdaki ölçümün gösterdiği duyuru selini üretir. Duyuru, göstergenin yanındaki ayrı bir kibar bölgeden ve seyrek yapılır.

Klavye ve Odak Sözleşmesi

Gösterge etkileşimli değildir; odak almaz ve sekme sırasında yer tutmaz. Klavye sözleşmesi göstergenin kendisinde değil, çevresindedir.

İptal düğmesi. On saniyeyi aşan işlerde iptal zorunludur ve iptal düğmesi odaklanabilir olmalıdır. İptal edildiğinde odak, gösterge kaybolduğu için bir yere taşınmalıdır: işi başlatan düğmeye.

Yer değiştiren odak. Gösterge, odaklı bir ögenin bulunduğu bölümün yerine konursa odak kaybolur ve belgenin başına düşer. Doğru kurulum, bölümü değiştirmeden içine gösterge koymak ya da odağı bilinçli olarak taşımaktır. Kip Pencereler dersindeki dönüş noktası kuralı burada da geçerlidir.

Devre dışı bırakılan denetimler. İş sürerken düğmeyi devre dışı bırakmak, odağı o düğmede olan kullanıcının odağını düşürebilir. Yerine aria-disabled ile işaretlemek ve tıklamayı yok saymak odağı korur.

Değer, Kalan Süre ve Duyuru Sıklığı

Yüzde hesabı basit ve güvenilirdir: biriken miktarın bütüne oranı. Kalan süre ise bir kestirimdir ve kestirimin kalitesi ölçülebilir. Aşağıdaki betik yirmi dört megabaytlık bir katalog dışa aktarmasını dört evreli bir akışla üretiyor, iki kestirim yöntemini karşılaştırıyor ve duyuru sıklığı politikalarını sınıyor.

// ilerleme.mjs — belirli ilerlemenin yuzdesi, kalan sure kestirimi ve duyuru sikligi

// Katalog disa aktarmasi: 24 MB, 200 ms'lik adimlarla gelen parcalar.
const TOPLAM = 24 * 1024 * 1024;
const ADIM = 200;                    // ms

// Yinelenebilir ornek: dogrusal esleyici uretec.
function uretec(tohum) {
  let s = tohum >>> 0;
  return () => ((s = (1664525 * s + 1013904223) >>> 0) / 4294967296);
}
const rastgele = uretec(20260728);

// Dort evre: yavas baslangic, hizli akis, duraklama, hizli bitis.
const EVRE = [
  { adim: 10, taban: 180_000, oynama: 0.5 },
  { adim: 20, taban: 900_000, oynama: 0.3 },
  { adim: 8,  taban: 40_000,  oynama: 0.8 },
  { adim: 40, taban: 700_000, oynama: 0.3 },
];

const artis = [];
for (const e of EVRE)
  for (let i = 0; i < e.adim; i++)
    artis.push(Math.max(0, Math.round(e.taban * (1 + e.oynama * (rastgele() * 2 - 1)))));

// Toplam tam olarak dolana kadar kes / tamamla.
let toplam = 0;
const gelen = [];
for (const a of artis) {
  if (toplam >= TOPLAM) break;
  const parca = Math.min(a, TOPLAM - toplam);
  toplam += parca;
  gelen.push(parca);
}
if (toplam < TOPLAM) gelen.push(TOPLAM - toplam);

// --- Yuzde ve kalan sure kestirimi ------------------------------------------
const ALFA = 0.25;                   // ustel agirlikli ortalamanin agirligi
const KOVA = 5000;                   // ms — gosterilen kestirimin yuvarlanma adimi
let birikmis = 0, hiz = null, oncekiGosterilen = Infinity;
const satir = [];
for (let i = 0; i < gelen.length; i++) {
  birikmis += gelen[i];
  const gecen = (i + 1) * ADIM;
  const yuzde = (birikmis / TOPLAM) * 100;
  const anlikHiz = gelen[i] / ADIM;                       // bayt/ms
  hiz = hiz === null ? anlikHiz : ALFA * anlikHiz + (1 - ALFA) * hiz;
  const naif = yuzde > 0 ? (gecen * (100 - yuzde)) / yuzde : Infinity;
  const agirlikli = hiz > 0 ? (TOPLAM - birikmis) / hiz : Infinity;
  // Gosterilen kestirim: kovaya yuvarlanir ve hicbir zaman artmaz.
  const gosterilen = Math.min(oncekiGosterilen, Math.ceil(agirlikli / KOVA) * KOVA);
  oncekiGosterilen = gosterilen;
  satir.push({ i: i + 1, gecen, yuzde, naif, agirlikli, gosterilen });
}

const gercekSure = gelen.length * ADIM;
console.log(`toplam ${(TOPLAM / 1024 / 1024).toFixed(0)} MB, ${gelen.length} adim, gercek sure ${gercekSure} ms`);
console.log("\nadim  gecen(ms)  yuzde   naif kalan  agirlikli kalan  gosterilen  gercek kalan");
for (const s of satir) {
  if (![1, 5, 10, 15, 25, 30, 34, 38, satir.length].includes(s.i)) continue;
  const yaz = (v) => (v === Infinity ? "—" : Math.round(v) + " ms");
  console.log(
    `${String(s.i).padStart(4)} ${String(s.gecen).padStart(10)} ${s.yuzde.toFixed(1).padStart(6)} ` +
      `${yaz(s.naif).padStart(12)} ${yaz(s.agirlikli).padStart(16)} ${yaz(s.gosterilen).padStart(11)} ` +
      `${(gercekSure - s.gecen + " ms").padStart(13)}`,
  );
}

// Kestirimlerin kararliligi: kalan surenin artmasi, en buyuk sicrama, gercekten sapma
function kararlilik(alan) {
  let artan = 0, enBuyuk = 0, sapmaToplam = 0, degisim = 0;
  for (let i = 1; i < satir.length; i++) {
    const fark = satir[i][alan] - satir[i - 1][alan];
    if (fark > 0) { artan++; enBuyuk = Math.max(enBuyuk, fark); }
    if (fark !== 0) degisim++;
    sapmaToplam += Math.abs(satir[i][alan] - (gercekSure - satir[i].gecen));
  }
  return { artan, enBuyuk, degisim, ortSapma: sapmaToplam / (satir.length - 1) };
}
console.log("\nkestirim      artan adim  en buyuk sicrama  ekran degisimi  ortalama mutlak sapma");
for (const [ad, alan] of [["naif", "naif"], ["agirlikli", "agirlikli"], ["gosterilen", "gosterilen"]]) {
  const k = kararlilik(alan);
  console.log(`${ad.padEnd(13)} ${String(k.artan).padStart(10)} ${(Math.round(k.enBuyuk) + " ms").padStart(17)} ` +
    `${String(k.degisim).padStart(15)} ${(Math.round(k.ortSapma) + " ms").padStart(22)}`);
}

// --- Duyuru sikligi ----------------------------------------------------------
// Iki duyuru arasindaki en az aralik, duyuru metninin okunma suresidir (onceki dersin modeli).
const TEPKI = 700, OKUMA_HIZI = 200;
const DUYURU = "yüzde 40 tamamlandı";
const EN_AZ_ARALIK = Math.round(TEPKI + (DUYURU.trim().split(/\s+/).length / OKUMA_HIZI) * 60000);
console.log(`\nduyuru metni: "${DUYURU}" -> en az aralik ${EN_AZ_ARALIK} ms`);
const POLITIKA = [
  { ad: "her adimda", esik: null, sure: null },
  { ad: "her %1", esik: 1, sure: null },
  { ad: "her %10", esik: 10, sure: null },
  { ad: "her %25", esik: 25, sure: null },
  { ad: "en az 2 sn arayla", esik: null, sure: 2000 },
  { ad: "her %10 + en az 2 sn", esik: 10, sure: 2000 },
];

console.log("politika            duyuru sayisi  en kisa aralik  okunamayan duyuru");
for (const p of POLITIKA) {
  const anlar = [];
  let sonYuzde = -Infinity, sonAn = -Infinity;
  for (const s of satir) {
    const yuzdeUygun = p.esik === null ? true : Math.floor(s.yuzde / p.esik) > Math.floor(sonYuzde / p.esik);
    const sureUygun = p.sure === null ? true : s.gecen - sonAn >= p.sure;
    if (p.esik === null && p.sure === null) { anlar.push(s.gecen); sonYuzde = s.yuzde; sonAn = s.gecen; continue; }
    if (yuzdeUygun && sureUygun) { anlar.push(s.gecen); sonYuzde = s.yuzde; sonAn = s.gecen; }
  }
  let enKisa = Infinity, okunamayan = 0;
  for (let i = 1; i < anlar.length; i++) {
    const aralik = anlar[i] - anlar[i - 1];
    enKisa = Math.min(enKisa, aralik);
    if (aralik < EN_AZ_ARALIK) okunamayan++;
  }
  console.log(`${p.ad.padEnd(20)} ${String(anlar.length).padStart(13)} ` +
    `${((enKisa === Infinity ? "—" : enKisa + " ms")).padStart(15)} ${String(okunamayan).padStart(18)}`);
}
toplam 24 MB, 45 adim, gercek sure 9000 ms

adim  gecen(ms)  yuzde   naif kalan  agirlikli kalan  gosterilen  gercek kalan
   1        200    0.6     33474 ms         33474 ms    35000 ms       8800 ms
   5       1000    3.9     24647 ms         25221 ms    30000 ms       8000 ms
  10       2000    7.5     24808 ms         23986 ms    25000 ms       7000 ms
  15       3000   27.8      7806 ms          4395 ms     5000 ms       6000 ms
  25       5000   65.0      2698 ms          1971 ms     5000 ms       4000 ms
  30       6000   81.8      1336 ms          1047 ms     5000 ms       3000 ms
  34       6800   82.6      1436 ms          2813 ms     5000 ms       2200 ms
  38       7600   82.8      1574 ms          7764 ms     5000 ms       1400 ms
  45       9000  100.0         0 ms             0 ms        0 ms          0 ms

kestirim      artan adim  en buyuk sicrama  ekran degisimi  ortalama mutlak sapma
naif                  11           2424 ms              44                4839 ms
agirlikli             13           2795 ms              44                5365 ms
gosterilen             0              0 ms               6                6405 ms

duyuru metni: "yüzde 40 tamamlandı" -> en az aralik 1600 ms
politika            duyuru sayisi  en kisa aralik  okunamayan duyuru
her adimda                      45          200 ms                 44
her %1                          36          200 ms                 35
her %10                         11          400 ms                  8
her %25                          5         1200 ms                  1
en az 2 sn arayla                5         2000 ms                  0
her %10 + en az 2 sn             5         2000 ms                  0

Birinci tablo kestirimin ne kadar kötü olduğunu gösteriyor. İlk adımda iki yöntem de otuz üç saniye söylüyor; gerçek kalan sekiz virgül sekiz saniyedir. Otuz dördüncü ve otuz sekizinci adımlarda —akışın duraksadığı evrede— ağırlıklı kestirim yediye yakın saniye söylerken gerçek kalan bir virgül dört saniyeye inmiştir. Duraksama boyunca kestirim yükselir, yani kullanıcı ekranda “kalan süre artıyor” görür.

İkinci tablo bunu üç sayıda topluyor. Naif kestirim kırk beş adımın on birinde artıyor, ağırlıklı kestirim on üçünde. Ağırlıklı yöntemin ortalama mutlak sapması naif yöntemden daha kötü çıkıyor: son ölçüme ağırlık vermek, duraksamayı geleceğe yayıyor. Buradan çıkan sonuç, daha iyi bir kestirim yöntemi aramak değil, kestirimin sunumunu değiştirmektir.

Üçüncü sütun bunu yapıyor. Gösterilen değer beş saniyelik kovalara yuvarlanıyor ve hiçbir zaman artmıyor. Sonuç: sıfır artış, kırk beş adımda yalnız altı ekran değişimi. Bedeli üçüncü sütunun son alanında görünür — ortalama mutlak sapma 6405 milisaniyeye, yani iki yöntemden de yükseğe çıkar. Bu bilinçli bir takastır: kalan süre zaten güvenilir değildir, o hâlde yanlış yöne gitmeyen ve seyrek değişen bir sunum, doğruya daha yakın ama zıplayan bir sunumdan iyidir.

Yüzdenin kendisi bu sorundan muaftır: biriken miktar azalmadığı için yüzde de azalmaz. Yüzdenin geri gittiği tek durum bütünün sonradan değişmesidir; bu durumda gösterge belirli olmaktan çıkar ve belirsize düşmelidir.

Son tablo duyuru sıklığını bağlıyor. Her adımda duyuru kırk beş duyuru üretiyor ve kırk dördü bir öncekinin okunma süresi dolmadan geliyor — yani hiçbiri okunmuyor. Yalnız yüzde eşiğine bakan politikalar da yetmiyor: hızlı evrede yüzde on eşiği dört yüz milisaniyede iki kez aşılıyor. Yeterli olan, zaman eşiğidir; iki saniyelik aralık duyuru sayısını beşe indiriyor ve okunamayan duyuruyu sıfıra çekiyor. Yüzde eşiği ile birlikte uygulandığında sonuç aynı kalıyor, çünkü kısıtlayan koşul zamandır.

Ölçülebilir Kısıtlar

4.1.3 Durum İletileri. İlerleme bildirimi odak taşımadan duyurulmalıdır; duyuru göstergenin kendisinden değil, yanındaki kibar bölgeden yapılır ve sıklığı yukarıdaki politikayla sınırlanır.

2.2.2 Duraklat, Durdur, Gizle. Beş saniyeden uzun süren ve kendiliğinden hareket eden bir gösterge duraklatılabilmeli ya da gizlenebilmelidir. Belirsiz göstergenin dönme hareketi bu ölçütün kapsamına girer; hareketi bir kısıtlama sorgusuyla azaltmak, ölçütü karşılamanın en ucuz yoludur — hareket yerine soluk alıp veren ya da hiç kıpırdamayan bir gösterge konur.

2.3.1 Üç Yanıp Sönme. Gösterge saniyede üçten fazla yanıp sönmemelidir. Hızlı yanıp sönen bir yükleme animasyonu bu eşiği aşabilir.

1.4.11 Metin Dışı Kontrast. İlerleme çubuğunun dolu kısmı ile boş kanalı arasındaki oran en az 3:1 olmalıdır; ayrıca çubuğun kendisi çevresindeki yüzeye karşı ayırt edilebilmelidir. Yalnız açık ve koyu tonla ayrılan ince bir çubuk bu eşiği kaçırır.

2.5.8 Hedef Boyutu (En Az). İptal düğmesi 24 x 24 piksellik eşiği karşılamalıdır.

Sık Yapılan Hata ve Nasıl Anlaşılır

Belirsiz göstergeye sıfır değer yazmak. aria-valuenow="0" yazan belirsiz bir gösterge, “hiç ilerlemedi” bilgisini yayar. Anlaşılması: ilerleme sürerken değerin sabit sıfır kalması.

Göstergeyi canlı bölge yapmak. Her değer değişiminde duyuru üretilir. Anlaşılması: duyuru sayısını adım sayısıyla karşılaştırmak; ikisi eşitse politika yoktur.

Kalan süreyi saniye saniye göstermek. Sayı ileri geri zıplar ve güven kaybettirir. Anlaşılması: kalan süre alanının artıp artmadığını izlemek; bir kez bile arttıysa sunum kusurludur.

Yüzdeyi zamandan uydurmak. Bütün bilinmediği hâlde geçen süreye bakarak yüzde üretmek, yüzde 90’da takılan bir çubuk üretir. Anlaşılması: göstergenin değerinin gerçek bir ölçümle mi yoksa sayaçla mı beslendiğine bakmak.

Özet

  • Belirsiz gösterge yalnız işin sürdüğünü, belirli gösterge tamamlanma oranını bildirir; belirsizlik aria-valuenow alanının bulunmamasıyla anlatılır, sıfır yazılarak değil.
  • Gösterge etkileşimli değildir ve odak almaz; klavye sözleşmesi iptal düğmesinde ve göstergenin yerini aldığı bölümdeki odağın korunmasındadır.
  • Yüzde güvenilirdir ve azalmaz; kalan süre bir kestirimdir ve ölçümde iki yöntem de saniyeler mertebesinde sapma üretir, ağırlıklı yöntem duraksamada daha da kötüleşir.
  • Kestirim, kovalara yuvarlanıp hiç artmayacak biçimde sunulduğunda ekran değişimi kırk dörtten altıya iner; bedeli daha büyük bir sapmadır ve bilinçli ödenir.
  • İlerleme duyurusu göstergeden değil ayrı bir kibar bölgeden yapılır ve zaman eşiğine bağlanır; yalnız yüzde eşiği hızlı evrelerde okunamayan duyuru üretir.
  • Kalıp 4.1.3, 2.2.2, 2.3.1, 1.4.11 ve 2.5.8 ölçütleriyle sınanır; hareketi azaltma tercihine uyan bir gösterge en ucuz uygunluk yoludur.

Sonraki Adım

Bu dersin göstergesi “çalışıyorum” dedi ama gelecek içeriğin yerini tutmadı. İçerik geldiğinde altındaki her şey aşağı itilirse kullanıcı baktığı noktayı kaybeder; ölçüldüğünde bu itilme bir sayıya dönüşür. Sonraki ders yer tutan yükleme biçimini —iskelet ekranı— belirtim olarak yazar: iskeletin erişilebilirlik ağacında nasıl görünmesi gerektiğini, tek bir bölge yerine birden çok bölge yenilendiğinde kaymanın nasıl biriktiğini ve iskeletin taahhüt ettiği yapının yanlış çıkmasının ne maliyet ürettiğini hesaplar.

İ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