Ders 17 / 23
Erişilebilir Hareket
Hareket duyarlılığının ne olduğu, büyük alanlı hareketin yerel hareketten ayrılması, hareket azaltma tercihinde toptan kapatmanın bedeli ve hareketi kaldırmak yerine değiştirmenin kuralları.
İçindekiler
Önceki ders hareketin tarayıcıya maliyetini ele aldı. Bir maliyet daha vardır ve o tarayıcıya değil kullanıcıya çıkar: ekrandaki hareket bazı kullanıcılarda baş dönmesi, bulantı ya da odaklanma güçlüğü üretir.
Duyarlı Tasarım konusunda prefers-reduced-motion sorgusu tanıtıldı ve kullanıcının bu
tercihi işletim sisteminden bildirdiği gösterildi. Bu ders sorgunun içine ne yazılacağını
ele alır: hangi hareket kaldırılır, hangisi korunur ve toptan kapatmanın bedeli nedir?
Hareket Duyarlılığı Neye Tepki Verir
Hareket duyarlılığı, iç kulaktaki denge düzeneğiyle görsel algının uyuşmamasından doğar. Ekranda büyük bir alan hareket ettiğinde görsel sistem kendi hareketimiz olduğu izlenimini üretir; denge düzeneği bunu doğrulamaz ve uyuşmazlık rahatsızlık verir.
Tetikleyen hareket türleri belirlidir: geniş alanların kayması, farklı hızlarda kayan katmanlar, yakınlaşma ve uzaklaşma, dönme, salınım. Tetiklemeyenler de belirlidir: saydamlık değişimi, renk değişimi, küçük alanlı yer değiştirme.
Ayrım “animasyon var mı yok mu” değildir. Ayrım, hareketin ne kadar alanı ne kadar yol boyunca taşıdığıdır.
Basamaklama ve Alan Hesabı
Aşağıdaki program iki şeyi hesaplıyor. Birincisi, hareket azaltma tercihinin iki durumu için kazanan bildirimleri basamaklama kurallarıyla çözüyor. İkincisi, dört hareketi kapladıkları alan ve kat ettikleri yola göre sınıflandırıyor.
// hareketazalt.mjs — tercih altinda basamaklama cozumu ve hareketin ekranda kapladigi alan // Basamaklama: once onem, sonra ozgulluk ucluleri, sonra yazim sirasi. const KURALLAR = [ { sira: 1, secici: ".gosterge", ozgulluk: [0, 1, 0], kosul: null, bildirim: { "animation-name": "nabiz", "animation-duration": "1400ms" } }, { sira: 2, secici: ".kart", ozgulluk: [0, 1, 0], kosul: null, bildirim: { "transition-duration": "240ms" } }, { sira: 3, secici: "*", ozgulluk: [0, 0, 0], kosul: "reduce", bildirim: { "animation-duration": "0.01ms", "transition-duration": "0.01ms" }, onemli: true }, { sira: 4, secici: ".gosterge", ozgulluk: [0, 1, 0], kosul: "reduce", bildirim: { "animation-name": "none" } }, ]; const uygular = (kural, tercih) => kural.kosul === null || kural.kosul === tercih; const buyuk = (a, b) => { // ozgulluk uclulerinin karsilastirmasi for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i]; return false; }; function coz(tercih, ozellikler) { const sonuc = {}; for (const ozellik of ozellikler) { let kazanan = null; for (const k of KURALLAR) { if (!uygular(k, tercih) || !(ozellik in k.bildirim)) continue; if (kazanan === null) { kazanan = k; continue; } const kOnem = !!k.onemli, zOnem = !!kazanan.onemli; if (kOnem !== zOnem) { if (kOnem) kazanan = k; continue; } if (buyuk(k.ozgulluk, kazanan.ozgulluk)) { kazanan = k; continue; } if (!buyuk(kazanan.ozgulluk, k.ozgulluk) && k.sira > kazanan.sira) kazanan = k; } sonuc[ozellik] = kazanan ? `${kazanan.bildirim[ozellik]} (kural ${kazanan.sira}: ${kazanan.secici}${kazanan.onemli ? " !important" : ""})` : "bildirim yok"; } return sonuc; } const OZELLIKLER = ["animation-name", "animation-duration", "transition-duration"]; for (const tercih of ["no-preference", "reduce"]) { console.log(`\n=== prefers-reduced-motion: ${tercih} ===`); const s = coz(tercih, OZELLIKLER); for (const o of OZELLIKLER) console.log(` ${o.padEnd(20)} -> ${s[o]}`); } // --- hareketin ekranda kapladigi oran --- // Buyuk alanli ve uzun yollu hareket, hareket duyarliligi olan kullanicilar icin // kucuk alanli hareketten farkli bir sinifta degerlendirilir. console.log("\n--- hareketin goruntu alanina orani (1280 x 800 birim) ---"); const EN = 1280, BOY = 800; const HAREKETLER = [ { ad: "kart vurgusu", en: 220, boy: 120, yol: 8 }, { ad: "gosterge nabzi", en: 12, boy: 12, yol: 1 }, { ad: "yan panel acilisi", en: 320, boy: 800, yol: 320 }, { ad: "arka plan kaymasi", en: 1280, boy: 800, yol: 240 }, ]; console.log("hareket".padEnd(20) + "alan orani".padStart(12) + "yol / ekran".padStart(14) + " siniflandirma"); for (const h of HAREKETLER) { const alan = (h.en * h.boy) / (EN * BOY); const yol = h.yol / Math.max(EN, BOY); const buyukAlan = alan >= 0.25 && yol >= 0.05; console.log( h.ad.padEnd(20) + `%${(alan * 100).toFixed(1)}`.padStart(12) + `%${(yol * 100).toFixed(1)}`.padStart(14) + " " + (buyukAlan ? "buyuk alanli hareket" : "yerel hareket"), ); }
=== prefers-reduced-motion: no-preference === animation-name -> nabiz (kural 1: .gosterge) animation-duration -> 1400ms (kural 1: .gosterge) transition-duration -> 240ms (kural 2: .kart) === prefers-reduced-motion: reduce === animation-name -> none (kural 4: .gosterge) animation-duration -> 0.01ms (kural 3: * !important) transition-duration -> 0.01ms (kural 3: * !important) --- hareketin goruntu alanina orani (1280 x 800 birim) --- hareket alan orani yol / ekran siniflandirma kart vurgusu %2.6 %0.6 yerel hareket gosterge nabzi %0.0 %0.1 yerel hareket yan panel acilisi %25.0 %25.0 buyuk alanli hareket arka plan kaymasi %100.0 %18.8 buyuk alanli hareket
İkinci blok bir ayıklama ölçütü veriyor. Kart vurgusu görüntü alanının yüzde üçünden azını kaplıyor ve sekiz birim yol alıyor; gösterge nabzı daha da küçük. Yan panel görüntü alanının dörtte birini kaplıyor ve genişliği kadar yol alıyor; arka plan kayması bütün ekranı kaplıyor.
Eşikler kesin sayılar değil, ayıklama araçlarıdır: kaplanan alan ile kat edilen yolun ikisinin de büyük olduğu hareketler ayrı bir sınıfa konur. Bu sınıftaki hareketler, tercih bildirildiğinde kaldırılır; yerel hareketler kısaltılarak korunabilir.
Toptan Kapatmanın Bedeli
İlk blok, sık yazılan bir kalıbı çözümlüyor: evrensel seçiciye !important ile sıfıra yakın
süre yazan bir kural. Sonuç okunduğunda kuralın gücü görünüyor — üçüncü kural, kendisinden
daha özgül olan birinci kuralın süresini de bastırıyor.
Süre neden değil de milisaniye? Sıfır süreli bir geçiş hiç çalışmaz ve bitiş olayı da yayılmaz; hareketin bitmesini bekleyen betikler o olayı hiç almaz. Çok kısa ama sıfırdan büyük bir süre, hareketi görünmez kılarken olay zincirini korur.
Kalıbın bedeli, ayrım yapmamasıdır. Aynı kural şunları da bastırır:
- Bir formun hata iletisinin yumuşak belirmesi — hareket değil, dikkat yönlendirmesidir.
- Bir açılır menünün kapanışı — anlık kapanma, ögenin nereye gittiğini gizler.
- Yükleniyor göstergesinin dönmesi — durum bilgisi taşır; kaldırılırsa yerine yazı konmalıdır.
Toptan kapatma bir başlangıç noktası olarak yazılabilir, ama bileşen bileşen gözden geçirilmemişse yarım bir çözümdür. Dördüncü kuralın yaptığı budur: göstergenin animasyonunu adıyla kapatır, yerine başka bir bildirim koyma sorumluluğunu üstlenir.
Kaldırmak Değil, Değiştirmek
Hareket azaltma tercihi “hiç animasyon istemiyorum” demek değildir; büyük alanlı ve yol kateden hareketin azaltılmasını ister. Bu, hareketle taşınan bilgiyi de silmek anlamına gelmez.
Üç kural yeterlidir:
- Yer değiştirmeyi saydamlıkla değiştir. Kayarak giren bir panel, yerinde belirebilir. Bilgi korunur, yol ortadan kalkar.
- Süreyi kısalt, döngüyü kaldır. Sonsuz yinelenen bir hareket, tercihte tek turluk bir duruma indirilir ya da durur.
- Durum taşıyan hareketin yerine yazı koy. Dönen bir gösterge yerine “veri bekleniyor” metni aynı bilgiyi taşır ve hiç hareket etmez.
Kayan yazı, otomatik ilerleyen tanıtım şeritleri ve arka plan videoları ayrı bir başlıktır. Erişilebilirlik ölçütlerinden 2.2.2, beş saniyeden uzun süren, kendiliğinden başlayan hareketin durdurulabilir olmasını ister; 2.3.1 saniyede üçten fazla parlamayı yasaklar; 2.3.3 ise etkileşimle tetiklenen hareketin kapatılabilmesini ister. İlk ikisi tercih bildirilmemiş olsa bile geçerlidir.
Yazım Yönü Seçimi
İki yazım yönü vardır. Birincisi hareketi varsayılan yazar ve reduce sorgusunda kapatır.
İkincisi hareketsiz yazar ve no-preference sorgusunda açar.
İkincisinin bir üstünlüğü vardır: sorgu değerlendirilemediğinde koşul sağlanmaz ve hareket açılmaz. Yani belirsizlik hareketsizlik lehine çözülür. Birinci yönde belirsizlik hareket lehine çözülür.
Bedeli, hareketin varsayılan durumdan ayrı bir yere yazılmasıdır; bileşenin biçemi iki parçaya bölünür. Seçim, hareketin sayfadaki ağırlığına göre yapılır: sayfa boyunca birkaç küçük geçiş varsa birinci yön yeterlidir, hareket tasarımın merkezindeyse ikinci yön güvenlidir.
İstasyon Sayfasında
/* hareket.css — 5. adım: hareket azaltma tercihi */ @media (prefers-reduced-motion: reduce) { .olcum-kartlari > .kart { transition-duration: 80ms; } .olcum-kartlari > .kart:hover, .olcum-kartlari > .kart:focus-within { transform: none; box-shadow: 0 0 0 2px var(--marka-koyu); } .istasyon-durumu[data-durum="bekliyor"] .gosterge { animation: none; will-change: auto; } .yan-bilgi { scroll-behavior: auto; } }
Kart vurgusu kaldırılmadı, değiştirildi: yükselme ve büyüme yerine bir çerçeve geliyor. Kullanıcı hangi kartın vurgulandığını yine görüyor, kart yer değiştirmiyor. Süre kısaltıldı ama sıfırlanmadı; ani değişim de rahatsız edicidir.
Gösterge animasyonu kapatıldı ve katman ipucu geri alındı. Durum bilgisinin yazıyla da
verilmesi gerekir; bu, biçem dosyasının değil belgenin sorumluluğudur ve .gosterge
ögesinin yanındaki metin bunu üstlenir.
scroll-behavior: auto bildirimi, yumuşak kaydırmayı ani kaydırmaya çevirir. Kaydırma tüm
görüntü alanını hareket ettirdiği için büyük alanlı hareket sınıfındadır.
Özet
- Hareket duyarlılığı, görsel algı ile denge düzeneğinin uyuşmamasından doğar; tetikleyen şey animasyonun varlığı değil, kaplanan alan ile kat edilen yolun büyüklüğüdür.
- Kaplanan alan ve yol oranı hesaplanarak hareketler ayıklanır: büyük alanlı olanlar kaldırılır, yerel olanlar kısaltılarak korunabilir.
- Evrensel seçiciye yazılan
!importantsüre kuralı bütün bildirimleri bastırır; bitiş olaylarının yayılması için süre sıfır değil sıfıra yakın yazılır. - Hareket kaldırılırken taşıdığı bilgi kaldırılmaz: yer değiştirme saydamlığa çevrilir, döngü durdurulur, durum taşıyan hareketin yerine yazı konur.
- Hareketi varsayılan yazıp kapatmakla, hareketsiz yazıp açmak arasındaki seçim, belirsizliğin hangi yöne çözüleceğini belirler.
Sonraki Adım
Bu konu boyunca hareket.css adında üçüncü bir biçem dosyası büyüdü. Artık aynı ögeye üç
ayrı dosyadan bildirim geliyor: görsel bildirimler istasyon.css dosyasında, yerleşim
duzen.css dosyasında, hareket burada. Sınıf adları da çoğaldı ve hangi adın hangi
bileşene ait olduğu yalnız alışkanlıkla biliniyor. Sonraki konu bu düzensizliği bir soruya
çevirir: biçem katmanı hangi kurallarla adlandırılır ve bölünür ki büyüdükçe okunur kalsın?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.