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-valuenowalanı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.