İçeriğe geç
academia.sh

Ders 16 / 22

Karşıtlık ve Erişilebilirlik

Kontrast oranı formülünün tanımı ve sınırları, eşikten renge ters hesap, büyük metin sınırının süreksizliği ve katalog arayüzünün tamamının toplu denetimi.

İçindekiler

Son iki derste 4.5 ve 3.0 sayıları her hesapta göründü. Bu sayılar kursun keyfî seçtiği ölçütler değil; nasıl ölçüleceği, hangi ögeleri kapsadığı ve hangi durumlarda muaf tutulduğu tanımlanmış eşiklerdir. Bu ders eşiklerin nereden geldiğini, hangi noktada tasarımı kısıtladığını ve katalog arayüzünün tamamının bunlara karşı nasıl denetleneceğini ele alır.

Ders adındaki iki sözcük iki ayrı şeye işaret eder. Karşıtlık, ilk derste tanıtılan algısal ilkedir: bir ögeyi çevresinden ayıran fark. Kontrast oranı ise bu farkın ölçülen niceliğidir. Karşıtlık bir tasarım aracıdır ve boyutta, ağırlıkta, biçimde ve konumda da kurulabilir; kontrast oranı yalnızca iki rengin bağıl parlaklıkları arasındaki ilişkiyi verir.

Formül ve Sınırları

Kontrast oranı (contrast ratio) iki rengin bağıl parlaklıklarından hesaplanır:

K=L1+0.05L2+0.05K = \frac{L_1 + 0.05}{L_2 + 0.05}

Burada L1L_1 açık olanın, L2L_2 koyu olanın bağıl parlaklığıdır. Paydadaki 0.05 sabiti, ekran yüzeyinden gelen ortam yansımasını temsil eder; bu sabit olmasaydı siyahın parlaklığı sıfır olduğu için oran sonsuza giderdi.

// esik.mjs — kontrast orani formulunun sinirlari, esikten renge ters hesap ve buyuk metin siniri

function kanal(v) {
  const s = v / 255;
  return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
}
const parlaklik = ([r, g, b]) => 0.2126 * kanal(r) + 0.7152 * kanal(g) + 0.0722 * kanal(b);
function kontrast(a, b) {
  const [x, y] = [parlaklik(a), parlaklik(b)].sort((p, q) => q - p);
  return (x + 0.05) / (y + 0.05);
}
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)].map((v) => Math.round(v * 255));
}
const onaltilik = (rgb) => "#" + rgb.map((v) => v.toString(16).padStart(2, "0")).join("");

const SIYAH = [0, 0, 0];
const BEYAZ = [255, 255, 255];
console.log(`parlaklik(siyah) = ${parlaklik(SIYAH).toFixed(4)}   parlaklik(beyaz) = ${parlaklik(BEYAZ).toFixed(4)}`);
console.log(`kontrast(siyah, beyaz) = ${kontrast(SIYAH, BEYAZ).toFixed(2)}:1   (formulun ust siniri)`);
console.log(`kontrast(beyaz, beyaz) = ${kontrast(BEYAZ, BEYAZ).toFixed(2)}:1   (alt siniri)`);

// Ters hesap: beyaz zemin uzerinde bir esigi saglayan en yuksek bagil parlaklik
console.log("\nesik  izin verilen en yuksek bagil parlaklik  esdeger gri (0-255)");
function griden(l) {
  let alt = 0, ust = 255;
  for (let i = 0; i < 40; i++) {
    const orta = (alt + ust) / 2;
    if (parlaklik([orta, orta, orta]) < l) alt = orta; else ust = orta;
  }
  return (alt + ust) / 2;
}
for (const esik of [3.0, 4.5, 7.0]) {
  const enYuksek = 1.05 / esik - 0.05;
  console.log(`${esik.toFixed(1).padStart(4)}  ${enYuksek.toFixed(4).padStart(38)}  ${griden(enYuksek).toFixed(1).padStart(18)}`);
}

// Formul yalniz parlakligi gorur: farkli tonlarda ayni orani veren renkler
console.log("\nton  aciklik  onaltilik  bagil parlaklik  beyaza karsi");
const HEDEF = 1.05 / 4.5 - 0.05;
for (const ton of [8, 42, 145, 214, 268]) {
  let alt = 0, ust = 100;
  for (let i = 0; i < 40; i++) {
    const orta = (alt + ust) / 2;
    if (parlaklik(hslRgb(ton, 60, orta)) < HEDEF) alt = orta; else ust = orta;
  }
  const l = (alt + ust) / 2;
  const rgb = hslRgb(ton, 60, l);
  console.log(
    `${String(ton).padStart(3)} ${l.toFixed(2).padStart(8)}%  ${onaltilik(rgb)}  ${parlaklik(rgb).toFixed(4).padStart(15)}  ${kontrast(rgb, BEYAZ).toFixed(2).padStart(11)}:1`
  );
}

// WCAG "buyuk metin" siniri: 18pt ya da kalin 14pt
const PT_PX = 96 / 72;
console.log(`\n18pt = ${(18 * PT_PX).toFixed(2)} px, 14pt = ${(14 * PT_PX).toFixed(2)} px`);
const buyukMu = (px, agirlik) => px >= 18 * PT_PX || (agirlik >= 700 && px >= 14 * PT_PX);
console.log("px  agirlik  buyuk metin  gecerli esik");
for (const [px, ag] of [[16, 400], [18, 700], [19, 700], [20, 400], [23, 400], [24, 400], [31, 700]]) {
  const b = buyukMu(px, ag);
  console.log(`${String(px).padStart(2)} ${String(ag).padStart(8)}  ${(b ? "evet" : "hayir").padStart(11)}  ${(b ? "3.0" : "4.5").padStart(12)}`);
}
parlaklik(siyah) = 0.0000   parlaklik(beyaz) = 1.0000
kontrast(siyah, beyaz) = 21.00:1   (formulun ust siniri)
kontrast(beyaz, beyaz) = 1.00:1   (alt siniri)

esik  izin verilen en yuksek bagil parlaklik  esdeger gri (0-255)
 3.0                                  0.3000               148.9
 4.5                                  0.1833               118.7
 7.0                                  0.1000                89.0

ton  aciklik  onaltilik  bagil parlaklik  beyaza karsi
  8    51.12%  #cd4c38           0.1843         4.48:1
 42    35.91%  #927225           0.1828         4.51:1
145    33.21%  #22884c           0.1847         4.47:1
214    50.49%  #3577cd           0.1836         4.50:1
268    59.44%  #935ad6           0.1837         4.49:1

18pt = 24.00 px, 14pt = 18.67 px
px  agirlik  buyuk metin  gecerli esik
16      400        hayir           4.5
18      700        hayir           4.5
19      700         evet           3.0
20      400        hayir           4.5
23      400        hayir           4.5
24      400         evet           3.0
31      700         evet           3.0

Birinci blok formülün aralığını veriyor: en düşük 1:1, en yüksek 21:1. Bu aralık yüzde değil oran ölçüsüdür; 10:1 ile 20:1 arasındaki fark, 2:1 ile 4:1 arasındaki farkla aynı katsayıdır ama gözle bakıldığında aynı büyüklükte değildir. Oranın kendisi doğrusal bir algı ölçüsü değildir.

İkinci blok ters hesabı veriyor ve tasarım tarafında doğrudan kullanılır. Beyaz zemin üzerinde 4.5 eşiğini sağlayan bir metin renginin bağıl parlaklığı en fazla 0.1833 olabilir; gri karşılığı 255 üzerinden yaklaşık 118.7. Bu, “metin rengi ne kadar açık olabilir” sorusunun sayısal yanıtıdır ve bir rengi denemek yerine doğrudan üretmeye yarar.

Üçüncü blok formülün en önemli sınırını gösteriyor. Beş farklı ton, hepsi beyaza karşı yaklaşık 4.5 kontrast üretiyor; ama bunu tutturdukları HSL açıklık değerleri yüzde 33.21 ile yüzde 59.44 arasında dağılıyor. Formül tonu görmez, yalnızca bağıl parlaklığı görür. Bunun iki sonucu var: aynı eşiği farklı tonlarda tutturmak farklı açıklık gerektirir, ve iki renk eşiği geçse bile birbirinden ayırt edilebilir olduklarını göstermez — bu, önceki dersteki bulgunun genel biçimidir.

Dördüncü blok bir başka sınırı, bir süreksizliği gösteriyor. Ölçüt metni iki kovaya ayırır: büyük ve büyük olmayan. Sınır 18 punto, kalın yazıda 14 puntodur; piksel karşılıkları 24 ve 18.67. Bunun sonucu, 18 piksellik kalın bir metnin 4.5 eşiğine, 19 piksellik kalın bir metnin ise 3.0 eşiğine tabi olmasıdır. Bir piksellik boyut değişikliği gereken kontrastı üçte bir oranında düşürüyor. Ölçüt sürekli bir işlev değil, basamaklı bir kuraldır.

Hangi Eşik Nereden Geliyor

Eşikler WCAG ölçütlerinde tanımlıdır. Dört ölçüt bu kursun kararlarını doğrudan bağlar:

  • 1.4.1 Renk Kullanımı. Renk, bilgi aktarmanın, bir eylemi belirtmenin ya da bir yanıtı ayırt etmenin tek görsel yolu olamaz. Önceki dersteki metin, simge ve konum kanalları bu ölçütün karşılığıdır. Bu ölçütün bir eşiği yoktur; bir yapı koşuludur.
  • 1.4.3 Kontrast (En Az). Metin ve metin görüntüleri için en az 4.5:1; büyük metin için 3:1.
  • 1.4.6 Kontrast (Gelişmiş). Aynı ayrım için 7:1 ve büyük metinde 4.5:1. Bu daha yüksek düzeydir; katalog arayüzünün gövde metni nötr 900 basamağında 15.74:1 taşıdığı için zaten karşılanır, ancak ikincil metinde karşılanmaz.
  • 1.4.11 Metin Dışı Kontrast. Bir bileşeni ya da durumunu tanımlamak için gereken görsel bilgi ile komşu renkler arasında en az 3:1. Kenarlık, odak halkası ve simge çizgileri bu ölçüte girer.

İki muafiyet vardır ve ikisi de karar noktasıdır. Etkin olmayan bileşenler, yani devre dışı bırakılmış denetimler, 1.4.3 kapsamı dışındadır. Salt süsleme amaçlı ve bilgi taşımayan metin de kapsam dışıdır. Muafiyet bir izin belgesi değildir: bir metin okunmuyorsa ölçüt onu muaf tutsa bile arayüz o bilgiyi vermiyordur.

Arayüzün Tamamını Denetlemek

Tek tek eşleşmelere bakmak yetmez, çünkü kaçırılan eşleşme fark edilmez. Denetim, arayüzde gerçekten oluşan bütün ön plan–zemin çiftlerinin listelenmesiyle yapılır.

// denetim.mjs — katalog arayuzunun tamaminin esiklere karsi toplu denetimi

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)].map((v) => Math.round(v * 255));
}
function kanal(v) {
  const s = v / 255;
  return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
}
const parlaklik = ([r, g, b]) => 0.2126 * kanal(r) + 0.7152 * kanal(g) + 0.0722 * kanal(b);
function kontrast(a, b) {
  const [x, y] = [parlaklik(a), parlaklik(b)].sort((p, q) => q - p);
  return (x + 0.05) / (y + 0.05);
}

const ACIKLIK = { "000": 100, "050": 97, 100: 92, 200: 84, 300: 74, 400: 62, 500: 50, 600: 40, 700: 31, 800: 22, 900: 14 };
const TON = { notr: [214, 8], ana: [214, 62], hata: [8, 68] };
const renk = (aile, b) => hslRgb(TON[aile][0], TON[aile][1], ACIKLIK[b]);

const ROL = {
  "yuzey":            ["notr", "000"],
  "yuzey-ikincil":    ["notr", "050"],
  "kenarlik":         ["notr", "500"],
  "metin-birincil":   ["notr", "900"],
  "metin-ikincil":    ["notr", "600"],
  "metin-devredisi":  ["notr", "400"],
  "eylem-ana":        ["ana", "600"],
  "eylem-ana-uzeri":  ["notr", "000"],
  "odak-halkasi":     ["ana", "500"],
  "secili-zemin":     ["ana", "100"],
  "hata-metni":       ["hata", "600"],
};

const PT_PX = 96 / 72;
const buyukMu = (px, ag) => px >= 18 * PT_PX || (ag >= 700 && px >= 14 * PT_PX);

// tur: "metin" | "metin-disi"
const DENETIM = [
  ["sayfa basligi",        "metin-birincil",  "yuzey",         "metin", 31, 700],
  ["kayit basligi",        "metin-birincil",  "yuzey",         "metin", 20, 600],
  ["yazar adi",            "metin-ikincil",   "yuzey",         "metin", 16, 400],
  ["ustveri (raf kodu)",   "metin-ikincil",   "yuzey-ikincil", "metin", 13, 400],
  ["odunc al dugmesi",     "eylem-ana-uzeri", "eylem-ana",     "metin", 16, 600],
  ["arama alani yer tut.", "metin-devredisi", "yuzey",         "metin", 16, 400],
  ["devre disi dugme",     "metin-devredisi", "yuzey-ikincil", "metin", 16, 600],
  ["hata iletisi",         "hata-metni",      "yuzey",         "metin", 13, 400],
  ["alan kenarligi",       "kenarlik",        "yuzey",         "metin-disi", 0, 0],
  ["odak halkasi",         "odak-halkasi",    "yuzey",         "metin-disi", 0, 0],
  ["secili satir zemini",  "secili-zemin",    "yuzey",         "metin-disi", 0, 0],
  ["secili satirda yazar", "metin-ikincil",   "secili-zemin",  "metin", 16, 400],
];

console.log("oge                    on plan           zemin           tur         esik  olculen  sonuc");
let kalan = 0;
for (const [oge, on, zemin, tur, px, ag] of DENETIM) {
  const esik = tur === "metin-disi" ? 3.0 : buyukMu(px, ag) ? 3.0 : 4.5;
  const k = kontrast(renk(...ROL[on]), renk(...ROL[zemin]));
  const gecti = k >= esik;
  if (!gecti) kalan++;
  console.log(
    `${oge.padEnd(22)} ${on.padEnd(17)} ${zemin.padEnd(15)} ${tur.padEnd(11)} ${esik.toFixed(1).padStart(4)} ${k.toFixed(2).padStart(8)}  ${gecti ? "gecti" : "KALDI"}`
  );
}
console.log(`\ntoplam ${DENETIM.length} eslesme, ${kalan} tanesi esigi gecmiyor`);

// Kalanlar icin: ayni ailede esigi saglayan ilk basamak
console.log("\nkalan eslesme      simdiki basamak  gereken en acik basamak");
for (const [oge, on, zemin, tur, px, ag] of DENETIM) {
  const esik = tur === "metin-disi" ? 3.0 : buyukMu(px, ag) ? 3.0 : 4.5;
  if (kontrast(renk(...ROL[on]), renk(...ROL[zemin])) >= esik) continue;
  const [aile] = ROL[on];
  const zeminRgb = renk(...ROL[zemin]);
  const uygun = Object.keys(ACIKLIK).find((b) => kontrast(renk(aile, b), zeminRgb) >= esik);
  console.log(
    `${oge.padEnd(18)} ${(ROL[on][0] + "-" + ROL[on][1]).padEnd(16)} ${uygun ? aile + "-" + uygun + " (" + kontrast(renk(aile, uygun), zeminRgb).toFixed(2) + ":1)" : "ailede yok"}`
  );
}

// Secili satir zemini koyulastirilirsa uzerindeki metin ne olur?
console.log("\nsecili zemin  zemin/yuzey  uzerindeki metin (notr-600)  metin esigi 4.5");
for (const b of ["100", "200", "300", "400"]) {
  const zemin = renk("ana", b);
  const kz = kontrast(zemin, renk(...ROL["yuzey"]));
  const km = kontrast(renk("notr", "600"), zemin);
  console.log(
    `${("ana-" + b).padEnd(13)} ${kz.toFixed(2).padStart(11)}:1 ${km.toFixed(2).padStart(28)}:1  ${km >= 4.5 ? "gecti" : "KALDI"}`
  );
}
oge                    on plan           zemin           tur         esik  olculen  sonuc
sayfa basligi          metin-birincil    yuzey           metin        3.0    15.74  gecti
kayit basligi          metin-birincil    yuzey           metin        4.5    15.74  gecti
yazar adi              metin-ikincil     yuzey           metin        4.5     5.89  gecti
ustveri (raf kodu)     metin-ikincil     yuzey-ikincil   metin        4.5     5.51  gecti
odunc al dugmesi       eylem-ana-uzeri   eylem-ana       metin        4.5     6.50  gecti
arama alani yer tut.   metin-devredisi   yuzey           metin        4.5     2.74  KALDI
devre disi dugme       metin-devredisi   yuzey-ikincil   metin        4.5     2.56  KALDI
hata iletisi           hata-metni        yuzey           metin        4.5     6.51  gecti
alan kenarligi         kenarlik          yuzey           metin-disi   3.0     4.11  gecti
odak halkasi           odak-halkasi      yuzey           metin-disi   3.0     4.59  gecti
secili satir zemini    secili-zemin      yuzey           metin-disi   3.0     1.23  KALDI
secili satirda yazar   metin-ikincil     secili-zemin    metin        4.5     4.80  gecti

toplam 12 eslesme, 3 tanesi esigi gecmiyor

kalan eslesme      simdiki basamak  gereken en acik basamak
arama alani yer tut. notr-400         notr-600 (5.89:1)
devre disi dugme   notr-400         notr-600 (5.51:1)
secili satir zemini ana-100          ana-400 (3.05:1)

secili zemin  zemin/yuzey  uzerindeki metin (notr-600)  metin esigi 4.5
ana-100              1.23:1                         4.80:1  gecti
ana-200              1.53:1                         3.85:1  KALDI
ana-300              2.06:1                         2.86:1  KALDI
ana-400              3.05:1                         1.94:1  KALDI

On iki eşleşmenin üçü kalıyor ve üçünün çözümü birbirinden farklı. Denetimin değeri başarısızlıkları bulmak değil, her birinin hangi türden bir başarısızlık olduğunu göstermektir.

Üç Başarısızlık, Üç Farklı Karar

Arama alanının yer tutucu metni 2.74 kontrast oranıyla eşiği kaçırıyor. Tablo düzeltmeyi veriyor: nötr 600 basamağı 5.89 üretir. Ancak bu düzeltme yeni bir sorun yaratır — yer tutucu metin o basamakta gerçek girdi metninden ayırt edilemez hâle gelir ve kullanıcı alanın dolu olduğunu sanar. Doğru karar rengi koyulaştırmak değil, yer tutucuyu etiket olarak kullanmayı bırakmaktır. Alanın adı alanın üstünde kalıcı bir etiket olarak durur; yer tutucu ya kaldırılır ya da yalnızca biçim örneği verir. Web Temelleri ve HTML kursundaki etiket–alan ilişkisi dersinde aynı sonuca erişilebilirlik tarafından varılmıştı; burada aynı sonuca kontrast hesabından varılıyor.

Devre dışı düğme 2.56 kontrast oranı taşıyor ve 1.4.3 ölçütü etkin olmayan bileşenleri muaf tutuyor. Yani bu satır bir uygunluk ihlali değil. Buna rağmen bir karar noktasıdır: devre dışı düğmenin üzerindeki metin okunamıyorsa, kullanıcı düğmenin ne yaptığını ve neden kullanılamadığını bilemez. Katalog arayüzünde “Ödünç al” düğmesi kayıt rafta değilken devre dışı kalıyor; kullanıcının okuması gereken şey tam da o düğmenin metni ve yanındaki açıklamadır. Muafiyet, düşük kontrastı zorunlu kılmaz; yalnızca serbest bırakır. Karar, düğmeyi devre dışı bırakmak yerine etkin tutup neden çalışmadığını söylemektir.

Seçili satır zemini 1.23 oranıyla kalıyor. Son tablo, koyulaştırarak düzeltmenin neden işe yaramadığını gösteriyor: zemin ana 400 basamağına taşındığında 3.05 ile eşiği geçiyor, ama üzerindeki ikincil metin 1.94’e düşerek çok daha ağır bir ihlal üretiyor. Zemini koyulaştırmak bir sorunu diğeriyle takas ediyor.

Çözüm 1.4.11 ölçütünün kendi ifadesinde saklı: eşik, bir durumu tanımlamak için gereken görsel bilgi için istenir. Seçili olma durumu yalnızca zemin rengiyle bildiriliyorsa zemin o bilgiyi taşıyor demektir ve eşiğe tabidir. Duruma ikinci bir gösterge eklenirse — satırın başında 3:1 eşiğini geçen bir dikey çubuk — zemin artık tek taşıyıcı olmaktan çıkar ve soluk kalabilir. Önceki dersin kuralı burada eşiği karşılamanın yolu hâline geliyor: ikinci kanal yalnızca dayanıklılık değil, uygunluk da üretir.

Denetimin Yeri

Bu denetimin sistem kurulurken yapılması, bileşenler yazıldıktan sonra yapılmasından ucuzdur. Rol tablosu ile eşleşme listesi birlikte tutulduğunda denetim bir veri işleme işine iner: girdi rol adlarıdır, çıktı geçen ve kalan eşleşmelerdir.

Eşleşme listesinin elle yazılması bir zayıflıktır — arayüze yeni bir eşleşme eklendiğinde listeye eklenmezse denetim onu görmez. Tasarım tarafında yapılması gereken, listenin var olması ve her yeni rol eşleşmesinin oraya yazılmasıdır.

Özet

  • Kontrast oranı, iki bağıl parlaklığın 0.05 kaydırmalı oranıdır; aralığı 1:1 ile 21:1 arasındadır ve doğrusal bir algı ölçüsü değildir.
  • Eşikten renge ters hesap yapılabilir: beyaz zeminde 4.5 eşiği, bağıl parlaklığı en fazla 0.1833 olan bir metin rengine izin verir.
  • Formül tonu görmez; aynı oranı veren renkler çok farklı açıklık değerlerinde durur ve eşiği geçmeleri birbirlerinden ayırt edilebilir oldukları anlamına gelmez.
  • Büyük metin sınırı basamaklı bir kuraldır; 18 ile 19 piksel arasında gereken kontrast 4.5’ten 3.0’a düşer.
  • Denetim tek tek değil, arayüzde gerçekten oluşan bütün ön plan–zemin çiftleri üzerinden yapılır; kaçırılan eşleşme fark edilmez.
  • Her başarısızlığın çözümü rengi koyulaştırmak değildir: kimi durumda öge kaldırılır, kimi durumda ikinci bir gösterge eklenerek renk tek taşıyıcı olmaktan çıkarılır.

Sonraki Adım

Bu ders bütün eşleşmeleri tek bir zemin ailesine karşı denetledi: açık yüzeyler ve koyu metin. Aynı arayüz koyu bir temada çalıştığında bu ilişkinin tamamı tersine döner. Ters çevirme işlemi göründüğü kadar mekanik değildir; rampanın basamaklarını başa baş takas etmek kontrast oranlarını korumaz ve işlevsel renkler koyu zeminde eşiği kaçırır. Sonraki ders koyu temayı bir renk listesi olarak değil bir dönüşüm olarak kurar, dönüşümün korunum koşulunu tanımlar ve dönüşümü aynı denetimden geçirir.

İ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