Ders 12 / 23
Kullanıcı Tercihleri
Renk şeması, kontrast, zorunlu renkler ve hareket azaltma tercihlerinin sorgulanması, koyu paletin kontrast denetimi, tercihlerin birlikte çözülmesi ve hareket azaltmanın neyi kapsadığı.
İçindekiler
Buraya kadar biçem hep ortamın ölçülebilir niteliklerine uyarlandı: görüntü alanının genişliği, kabın ölçüsü, aygıtın piksel oranı. Bunların hepsi aygıtın özellikleriydi.
Kullanıcının kendisi de bir tercih bildirir. İşletim sistemi ya da tarayıcı ayarlarında koyu bir renk şeması seçmiş, kontrastın artırılmasını istemiş ya da hareketin azaltılmasını istemiş olabilir. Bu tercihler aynı sorgu yazımıyla okunur ve sayfa bunlara uyar.
Tercih Bir Ortam Özelliğidir
Dört ortam özelliği kullanıcı tercihini bildirir.
prefers-color-scheme açık ya da koyu bir yüzey tercihini verir; değerleri light, dark ve
no-preference. prefers-contrast kontrastın artırılması ya da azaltılması isteğini bildirir.
prefers-reduced-motion hareketin azaltılması isteğini taşır ve tek anlamlı değeri reduce.
forced-colors ise ayrı bir durumu bildirir: kullanıcı, sayfanın renklerinin yerine kendi
belirlediği sınırlı bir palete zorlanmasını istemiştir.
Dördü de ortam sorgularıyla aynı biçimde yazılır ve aynı basamaklama kurallarına uyar. Ayrım soruda değil öznededir: genişlik aygıtın, tercih kullanıcınındır.
Bir tasarım kararı hemen ortaya çıkar. Tercih sorguları biçemi eklemeli yazılmaya zorlar:
taban biçem no-preference durumunu tarif eder, sorgu bloğu yalnız değişeni yazar. Ters yönde
yazıldığında — koyu şema taban alınıp açık şema sorguyla geri alındığında — tercih bildirmemiş
bir kullanıcı, tasarımın istisnası olur.
Koyu Şemanın Kontrast Denetimi
Koyu şema, açık şemanın renklerinin ters çevrilmesi değildir. Renklerin bağıl parlaklıkları doğrusal olmadığı için, zeminin değişmesi ön plandaki her rengin oranını yeniden hesaplamayı gerektirir.
CSS ile Görsel Sunum kursunda tanıtılan kontrast oranı, iki rengin bağıl parlaklıklarından hesaplanır. Aşağıdaki program istasyon paletini iki şemada denetliyor.
// kontrast.mjs — acik ve koyu renk semasinin kontrast oranlarinin denetimi // Bagil parlaklik ve kontrast orani WCAG tanimina gore hesaplanir. function hslRgb(h, s, l) { s /= 100; l /= 100; const k = (n) => (n + h / 30) % 12; const a = s * Math.min(l, 1 - l); const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))); return [f(0), f(8), f(4)]; } function bagilParlaklik([r, g, b]) { const d = (c) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)); return 0.2126 * d(r) + 0.7152 * d(g) + 0.0722 * d(b); } function oran(renk1, renk2) { const l1 = bagilParlaklik(renk1), l2 = bagilParlaklik(renk2); const [buyuk, kucuk] = l1 > l2 ? [l1, l2] : [l2, l1]; return (buyuk + 0.05) / (kucuk + 0.05); } // acik sema: CSS ile Gorsel Sunum kursunda kurulan palet const ACIK = { zemin: [203, 18, 97], metin: [211, 29, 16], metinSoluk: [211, 12, 45], uyari: [0, 66, 33], cizgi: [207, 15, 86], }; // koyu sema, yalniz zemin ve ana metin degistirilmis; kalanlar oldugu gibi birakilmis const KOYU_EKSIK = { zemin: [211, 22, 12], metin: [211, 20, 92], metinSoluk: ACIK.metinSoluk, uyari: ACIK.uyari, cizgi: ACIK.cizgi, }; const HEDEF = { metin: 4.5, metinSoluk: 4.5, uyari: 4.5, cizgi: 3 }; function tablo(baslik, sema) { console.log(`\n--- ${baslik} ---`); console.log(`zemin: hsl(${sema.zemin.join(" ")})`); console.log("on plan hsl hedef oran sonuc"); for (const ad of ["metin", "metinSoluk", "uyari", "cizgi"]) { const o = oran(hslRgb(...sema[ad]), hslRgb(...sema.zemin)); console.log( `${ad.padEnd(11)} ${("hsl(" + sema[ad].join(" ") + ")").padEnd(14)}` + `${(HEDEF[ad] + ":1").padStart(6)} ${o.toFixed(2).padStart(5)} ${(o >= HEDEF[ad] ? "gecer" : "KALIR").padStart(5)}`, ); } } tablo("acik sema", ACIK); tablo("koyu sema: yalniz zemin ve metin degistirilmis", KOYU_EKSIK); // duzeltme: her on plan icin hedefi saglayan en dusuk aydinlik degeri aranir console.log("\n--- koyu semada gereken aydinlik degerleri ---"); console.log("on plan su anki L gereken L su anki oran duzeltilmis oran"); for (const ad of ["metin", "metinSoluk", "uyari", "cizgi"]) { const [h, s, l] = KOYU_EKSIK[ad]; let gereken = l; while (gereken <= 100 && oran(hslRgb(h, s, gereken), hslRgb(...KOYU_EKSIK.zemin)) < HEDEF[ad]) { gereken += 0.5; } console.log( `${ad.padEnd(11)} ${l.toFixed(1).padStart(9)} ${gereken.toFixed(1).padStart(9)} ` + `${oran(hslRgb(h, s, l), hslRgb(...KOYU_EKSIK.zemin)).toFixed(2).padStart(12)} ` + `${oran(hslRgb(h, s, gereken), hslRgb(...KOYU_EKSIK.zemin)).toFixed(2).padStart(16)}`, ); }
--- acik sema --- zemin: hsl(203 18 97) on plan hsl hedef oran sonuc metin hsl(211 29 16) 4.5:1 13.93 gecer metinSoluk hsl(211 12 45) 4.5:1 4.59 gecer uyari hsl(0 66 33) 4.5:1 8.56 gecer cizgi hsl(207 15 86) 3:1 1.30 KALIR --- koyu sema: yalniz zemin ve metin degistirilmis --- zemin: hsl(211 22 12) on plan hsl hedef oran sonuc metin hsl(211 20 92) 4.5:1 13.90 gecer metinSoluk hsl(211 12 45) 4.5:1 3.42 KALIR uyari hsl(0 66 33) 4.5:1 1.83 KALIR cizgi hsl(207 15 86) 3:1 12.08 gecer --- koyu semada gereken aydinlik degerleri --- on plan su anki L gereken L su anki oran duzeltilmis oran metin 92.0 92.0 13.90 13.90 metinSoluk 45.0 53.0 3.42 4.54 uyari 33.0 61.0 1.83 4.53 cizgi 86.0 86.0 12.08 12.08
Açık şemada üç metin rengi de 4,5:1 ölçütünü karşılıyor. Ayraç çizgisi 1,30 oranıyla 3:1 ölçütünün altında — bu bir kusur değil, kapsam sorusudur. WCAG 1.4.11 ölçütü metin dışı kontrastı arayüz bileşenlerinin ve durum göstergelerinin sınırları için ister; bir çizelgedeki satır ayracı süsleyicidir. Aynı renk bir girdi kenarlığı ya da odak göstergesi olarak kullanılacaksa ölçütü karşılamaz ve koyulaştırılması gerekir.
Koyu şemada tablo tersine dönüyor. Zemin ve ana metin değiştirilmiş; ikisi arasında 13,90 oran var ve sorun görünmüyor. Ama değiştirilmemiş iki renk düşüyor: soluk metin 3,42, uyarı rengi 1,83. Uyarı rengi, kullanıcıyı uyarması gereken renktir ve koyu zeminde neredeyse okunamaz.
Üçüncü blok düzeltmeyi hesaplıyor. Soluk metnin aydınlık değeri 45’ten 53’e, uyarı renginin 33’ten 61’e çıkarıldığında ölçüt karşılanıyor. Uyarı renginde gereken 28 puanlık artış, doğrudan çevirmenin neden yetmediğini gösteriyor: kırmızının bağıl parlaklığı düşüktür, koyu zeminde ayırt edilmesi için çok daha açık bir tona çıkması gerekir.
Kural şudur: koyu şema, açık şemanın her renginin yeniden hesaplanmasıdır. Değiştirilmeyen renk, sessizce ölçüt dışına düşen renktir.
Tercihlerin Birlikte Çözülmesi
Kullanıcı birden çok tercihi aynı anda bildirebilir. Sorgular birbirinden bağımsız olduğu için sonuç, eşleşen bütün blokların basamaklamadaki birleşimidir.
// tercih.mjs — kullanici tercihi sorgularinin cozulmesi ve sonucun belirlenmesi // Kurallar kaynak sirasinda; kosulu dogru olanlarin sonuncusu kazanir. const KURALLAR = [ { kosul: null, deger: { zemin: "acik", cizgi: "ince", gecis: "200ms", kaydirma: "yumusak" } }, { kosul: "prefers-color-scheme: dark", deger: { zemin: "koyu" } }, { kosul: "prefers-contrast: more", deger: { cizgi: "kalin" } }, { kosul: "prefers-reduced-motion: reduce", deger: { gecis: "1ms", kaydirma: "ani" } }, { kosul: "forced-colors: active", deger: { zemin: "sistem", cizgi: "sistem" } }, ]; const PROFILLER = [ { ad: "varsayilan", "prefers-color-scheme": "light", "prefers-contrast": "no-preference", "prefers-reduced-motion": "no-preference", "forced-colors": "none" }, { ad: "koyu sema", "prefers-color-scheme": "dark", "prefers-contrast": "no-preference", "prefers-reduced-motion": "no-preference", "forced-colors": "none" }, { ad: "koyu + az hareket", "prefers-color-scheme": "dark", "prefers-contrast": "no-preference", "prefers-reduced-motion": "reduce", "forced-colors": "none" }, { ad: "yuksek kontrast", "prefers-color-scheme": "light", "prefers-contrast": "more", "prefers-reduced-motion": "no-preference", "forced-colors": "none" }, { ad: "zorunlu renkler", "prefers-color-scheme": "dark", "prefers-contrast": "more", "prefers-reduced-motion": "reduce", "forced-colors": "active" }, ]; function eslesir(kosul, profil) { if (kosul === null) return true; const [ozellik, deger] = kosul.split(":").map((s) => s.trim()); return profil[ozellik] === deger; } function coz(profil) { const sonuc = {}; const eslesen = []; for (const k of KURALLAR) { if (!eslesir(k.kosul, profil)) continue; eslesen.push(k.kosul ?? "taban"); Object.assign(sonuc, k.deger); } return { sonuc, eslesen }; } console.log("kurallar:"); for (const k of KURALLAR) { console.log(` ${(k.kosul ?? "(taban)").padEnd(32)} ${JSON.stringify(k.deger)}`); } console.log("\nprofil zemin cizgi gecis kaydirma eslesen kural sayisi"); for (const p of PROFILLER) { const { sonuc, eslesen } = coz(p); console.log( `${p.ad.padEnd(17)} ${sonuc.zemin.padEnd(7)} ${sonuc.cizgi.padEnd(7)} ` + `${sonuc.gecis.padStart(5)} ${sonuc.kaydirma.padEnd(8)} ${String(eslesen.length).padStart(19)}`, ); } // color-scheme bildirimi: tarayici denetimleri ve kaydirma cubugu hangi semayi kullanir console.log("\n--- color-scheme bildiriminin tarayici denetimlerine etkisi ---"); console.log("kullanici tercihi color-scheme bildirimi denetimler sayfa uyumlu mu"); for (const tercih of ["light", "dark"]) { for (const bildirim of ["normal", "light", "light dark"]) { const destekli = bildirim.split(" ").includes(tercih); const denetim = destekli ? tercih : bildirim === "normal" ? "light" : bildirim.split(" ")[0]; const sayfa = tercih; // sayfanin kendi renkleri sorguyla zaten uyarlanmis console.log( `${tercih.padEnd(17)} ${bildirim.padEnd(22)} ${denetim.padEnd(10)} ${sayfa.padEnd(7)} ` + `${(denetim === sayfa ? "evet" : "HAYIR").padStart(9)}`, ); } } // hareket azaltma neyi azaltir: ozellik turune gore politika console.log("\n--- hareket azaltma tercihinde ozellik turlerine gore sure ---"); const HAREKETLER = [ { ad: "translate/scale", tur: "yer degistirme", taban: 300 }, { ad: "rotate", tur: "yer degistirme", taban: 400 }, { ad: "opacity", tur: "gorunurluk", taban: 200 }, { ad: "background-color", tur: "renk", taban: 150 }, { ad: "surekli donen bant", tur: "surekli hareket", taban: 8000 }, ]; console.log("ozellik tur taban azaltilmis karar"); for (const h of HAREKETLER) { const kapatilir = h.tur === "yer degistirme" || h.tur === "surekli hareket"; const yeni = kapatilir ? 0 : h.taban; const karar = kapatilir ? "kaldirilir" : "korunur"; console.log( `${h.ad.padEnd(22)} ${h.tur.padEnd(16)} ${String(h.taban + "ms").padStart(6)} ` + `${String(yeni + "ms").padStart(10)} ${karar.padStart(11)}`, ); }
kurallar:
(taban) {"zemin":"acik","cizgi":"ince","gecis":"200ms","kaydirma":"yumusak"}
prefers-color-scheme: dark {"zemin":"koyu"}
prefers-contrast: more {"cizgi":"kalin"}
prefers-reduced-motion: reduce {"gecis":"1ms","kaydirma":"ani"}
forced-colors: active {"zemin":"sistem","cizgi":"sistem"}
profil zemin cizgi gecis kaydirma eslesen kural sayisi
varsayilan acik ince 200ms yumusak 1
koyu sema koyu ince 200ms yumusak 2
koyu + az hareket koyu ince 1ms ani 3
yuksek kontrast acik kalin 200ms yumusak 2
zorunlu renkler sistem sistem 1ms ani 5
--- color-scheme bildiriminin tarayici denetimlerine etkisi ---
kullanici tercihi color-scheme bildirimi denetimler sayfa uyumlu mu
light normal light light evet
light light light light evet
light light dark light light evet
dark normal light dark HAYIR
dark light light dark HAYIR
dark light dark dark dark evet
--- hareket azaltma tercihinde ozellik turlerine gore sure ---
ozellik tur taban azaltilmis karar
translate/scale yer degistirme 300ms 0ms kaldirilir
rotate yer degistirme 400ms 0ms kaldirilir
opacity gorunurluk 200ms 200ms korunur
background-color renk 150ms 150ms korunur
surekli donen bant surekli hareket 8000ms 0ms kaldirilir
Birinci tablo, tercihlerin birbirini engellemediğini gösteriyor. Koyu şema ve hareket azaltma birlikte bildirildiğinde ikisi de uygulanır; her sorgu kendi özelliğini yazar, birbirinin yazdığını ezmez.
Son satır bir istisnayı işaretliyor. Zorunlu renkler etkinken sayfanın renk bildirimlerinin çoğu yok sayılır ve yerine sistemin belirlediği sınırlı bir palet konur. Bu, kullanıcı için kesin bir gereksinimdir; sayfanın yapması gereken, renk yerine başka bir ayrım aracı bırakmaktır. Yalnız renkle ayrılan bir durum — örneğin eksik ölçümü kırmızıyla göstermek — zorunlu renklerde tamamen kaybolur; aynı bilgi bir simge ya da metinle de verilmelidir.
Tarayıcının Kendi Parçaları
İkinci tablo, sayfanın renklerini değiştirmenin yetmediği bir alanı gösteriyor.
Kaydırma çubuğu, form denetimlerinin varsayılan görünümü, metin seçimi rengi — bunlar tarayıcının
çizdiği parçalardır ve sayfanın renk bildirimlerinden etkilenmezler. Hangi şemayı kullanacaklarını
color-scheme bildirimi söyler.
Bildirim yazılmadığında ya da yalnız light yazıldığında, kullanıcı koyu şema tercih etse bile
denetimler açık kalır: koyu bir sayfada parlak beyaz bir kaydırma çubuğu ve açık zeminli girdiler
görünür. color-scheme: light dark bildirimi iki şemayı da desteklediğini bildirir ve denetimler
kullanıcının tercihini izler.
Bildirimin ikinci bir sonucu daha vardır: light-dark() fonksiyonu ancak bir şema seçili
olduğunda çalışır. Fonksiyon iki değer alır ve etkin şemaya göre birini seçer; koyu şema
kurallarını ayrı bir sorgu bloğuna yazmak yerine değer düzeyinde ayırmayı sağlar.
Hareket Azaltma Neyi Azaltır
Üçüncü tablo, bu dersin bıraktığı asıl soruya geliyor.
prefers-reduced-motion tercihinin adı yanıltıcı olabilir: tercih, animasyonun kapatılmasını
değil hareketin azaltılmasını ister. Ayrım, hangi özelliğin değiştiğine bakılarak yapılır.
Bir ögenin yer değiştirmesi — kayması, dönmesi, ölçeklenmesi — göz için bir devinim üretir. Vestibüler duyarlılığı olan kullanıcılarda büyük ya da beklenmedik devinimler baş dönmesi ve mide bulantısına yol açabilir. Tercihi bildiren kullanıcı bu devinimden kaçınmak ister.
Bir ögenin görünürlüğünün ya da renginin değişmesi devinim üretmez. Bir düğmenin üzerine gelindiğinde arka planının 150 milisaniyede değişmesi, hareket azaltma tercihinde de korunabilir; hatta korunmalıdır, çünkü bu geçiş durum değişikliğinin okunmasını kolaylaştırır.
Tablodaki karar sütunu bu ayrımı uyguluyor: yer değiştirme üreten ve sürekli tekrarlanan hareketler kaldırılıyor, görünürlük ve renk geçişleri korunuyor. Bütün geçişleri tek bir kuralla sıfırlamak — evrensel seçiciye sıfır süre yazmak — kolay bir çözümdür ama arayüzü ani ve okunması zor hâle getirir.
WCAG 2.3.3 ölçütü bu politikayı doğrudan tarif eder: etkileşimle tetiklenen hareket animasyonları, işlev için gerekli değilse kapatılabilmelidir.
/* duzen.css — 12. adım: kullanıcı tercihleri */ :root { color-scheme: light dark; --zemin: hsl(203 18% 97%); --metin: hsl(211 29% 16%); --metin-soluk: hsl(211 12% 45%); --uyari: hsl(0 66% 33%); --cizgi: hsl(207 15% 86%); --gecis-suresi: 200ms; } @media (prefers-color-scheme: dark) { :root { --zemin: hsl(211 22% 12%); --metin: hsl(211 20% 92%); --metin-soluk: hsl(211 12% 53%); --uyari: hsl(0 66% 61%); --cizgi: hsl(207 15% 30%); } } @media (prefers-contrast: more) { :root { --metin-soluk: var(--metin); --cizgi: var(--metin); } } @media (prefers-reduced-motion: reduce) { :root { --gecis-suresi: 1ms; } html { scroll-behavior: auto; } } @media (forced-colors: active) { .olcum-cizelgesi .eksik { forced-color-adjust: none; } .olcum-cizelgesi .eksik::before { content: "! "; } }
Beş blok, beş ayrı soru. Taban blok açık şemayı ve varsayılan süreyi tanımlıyor; kalan dördü yalnız değişen özel özellikleri yazıyor. Koyu şemadaki aydınlık değerleri yukarıdaki hesabın sonucu: soluk metin 53, uyarı 61. Zorunlu renkler bloğu ise renk yerine metin ayrımı ekliyor — eksik ölçüm, renk kaybolduğunda da bir işaretle görünür kalıyor.
Bütün bildirimler özel özellikler üzerinden geçtiği için bileşen kuralları hiç değişmiyor. Bir
kart, bir çizelge satırı ya da bir düğme var(--metin) yazdığında dört tercihin bileşimi zaten
çözülmüş olur.
Özet
- Dört ortam özelliği kullanıcı tercihini bildirir: renk şeması, kontrast, hareket azaltma ve zorunlu renkler; hepsi ortam sorgularıyla aynı biçimde yazılır.
- Taban biçem tercih bildirmemiş kullanıcıyı tarif eder; sorgu blokları yalnız değişeni yazar ve hiçbirini geri almaz.
- Koyu şema, açık şemanın çevrilmesi değildir: zemin değiştiğinde her ön plan renginin oranı yeniden hesaplanır; istasyon paletinde uyarı renginin aydınlık değeri 33’ten 61’e çıkmak zorundadır.
- Tercihler birbirinden bağımsızdır ve birlikte bildirildiklerinde hepsi uygulanır; zorunlu renkler etkinken yalnız renkle verilen ayrımlar kaybolur ve metin ya da simge yedeği gerekir.
color-schemebildirimi tarayıcının kendi çizdiği parçaların hangi şemayı izleyeceğini söyler; yazılmadığında koyu bir sayfada açık denetimler kalır.- Hareket azaltma tercihi yer değiştirme üreten ve sürekli tekrarlanan hareketleri kaldırır; görünürlük ve renk geçişleri korunur, çünkü devinim üretmezler.
Sonraki Adım
Son tablo bir ayrım kurdu ama tanımı yapmadan kullandı: “yer değiştirme üreten” özellik hangisi? Bir ögeyi kaydıran, döndüren ve ölçekleyen bildirimlerin ortak bir adı ve ortak bir matematiği vardır; ikisini de görmeden hangi hareketin azaltılacağına karar vermek sezgiye kalır. Sonraki konu buradan başlar: bir öge kendi kutusunu bozmadan nasıl ötelenir, ölçeklenir ve döndürülür, bu işlemler hangi sırayla uygulanır ve sıra neden sonucu değiştirir?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.