İçeriğe geç
academia.sh

Ders 15 / 22

İşlevsel Renkler

Başarı, uyarı, hata ve bilgi anlamlarının renk ailelerine bağlanması, aynı basamağın ailelere göre farklı kontrast üretmesi, bildirim üçlüsünün kendi zeminine göre denetimi ve rengin tek kanal olamayacağı.

İçindekiler

Önceki ders rengi görsel bir göreve bağladı: yüzey, metin, kenarlık, eylem. Bu roller arayüzün nasıl göründüğünü belirler ama ne söylediğini belirlemez. Katalog arayüzünde renk ikinci bir iş daha yapıyor. Ödünç alma işlemi tamamlandığında bir bildirim beliriyor; kayıt rafta bulunmuyorsa bir uyarı; ISBN alanına geçersiz bir değer girildiğinde bir hata; iade tarihinin yaklaştığını söyleyen bir bilgi satırı.

Bu dört durum, arayüzün kullanıcıya söylediği dört ayrı şeydir ve renkle işaretlenir. Sorulacak soru şudur: bu renkler nasıl seçilir, seçildikten sonra nasıl denetlenir ve renk bu işi tek başına yapabilir mi?

Dört Anlam, Dört Aile

İşlevsel renkler (semantic colors), görünüşü değil durumu bildiren renklerdir. Dört temel anlam ayrılır:

  • Başarı. İstenen sonuç gerçekleşti. Katalog arayüzünde ödünç alma işleminin tamamlanması.
  • Uyarı. İşlem sürebilir ama bir koşul dikkat gerektiriyor. Kaydın rafta değil, başka bir şubede bulunması.
  • Hata. İşlem sürdürülemez, kullanıcıdan bir düzeltme bekleniyor. Geçersiz ISBN girdisi.
  • Bilgi. Durumla ilgili nötr bir bildirim, eylem gerekmiyor. İade tarihinin bildirilmesi.

Bu dört anlamın renklerle eşleşmesi doğal bir ilişki değil, öğrenilmiş bir sözleşmedir. Sözleşme evrensel de değildir; aynı renk farklı kültürlerde farklı okunur. Buradan çıkan sonuç, sistemin tasarımını doğrudan etkiler: renk bir ipucu olarak kullanılabilir, tek başına taşıyıcı olarak kullanılamaz. Bu dersin son iki bölümü bunun hesabını verir.

Her anlam bir renk değil, bir aile alır. Bunun nedeni bir anlamın arayüzde tek bir biçimde görünmemesidir: aynı hata durumu bir bildirim kutusunun soluk zemininde, bir form alanının kenarlığında ve bir metin satırında görünür. Üçü aynı ailenin farklı basamaklarıdır.

Aynı Basamak Ailelere Göre Farklı İş Yapar

Önceki derste bulunan sonuç burada belirleyici hâle gelir: aynı açıklık değeri farklı tonlarda farklı bağıl parlaklık üretir. Dört işlevsel aile için bu fark, hangi basamağın metin taşıyabileceğini doğrudan değiştirir.

// islevsel.mjs — islevsel renk ailelerinin ayni basamakta ayni isi yapmadiginin 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));
}
const onaltilik = (rgb) => "#" + rgb.map((v) => v.toString(16).padStart(2, "0")).join("");
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 = { 100: 92, 200: 84, 300: 74, 400: 62, 500: 50, 600: 40, 700: 31, 800: 22 };
const AILE = {
  basari: [145, 54],
  uyari:  [42, 92],
  hata:   [8, 68],
  bilgi:  [214, 62],
};
const BEYAZ = [255, 255, 255];
const renk = (aile, b) => hslRgb(AILE[aile][0], AILE[aile][1], ACIKLIK[b]);

console.log("aile    " + Object.keys(ACIKLIK).map((b) => b.padStart(8)).join(""));
for (const aile of Object.keys(AILE)) {
  const satir = Object.keys(ACIKLIK)
    .map((b) => kontrast(renk(aile, b), BEYAZ).toFixed(2).padStart(8))
    .join("");
  console.log(`${aile.padEnd(7)} ${satir}`);
}

console.log("\naile    metin basamagi (>=4.5:1)  dolgu basamagi (>=3.0:1)");
for (const aile of Object.keys(AILE)) {
  const bas = Object.keys(ACIKLIK);
  const metin = bas.find((b) => kontrast(renk(aile, b), BEYAZ) >= 4.5);
  const dolgu = bas.find((b) => kontrast(renk(aile, b), BEYAZ) >= 3.0);
  console.log(
    `${aile.padEnd(7)} ${(metin + " (" + onaltilik(renk(aile, metin)) + ")").padStart(24)}` +
      ` ${(dolgu + " (" + onaltilik(renk(aile, dolgu)) + ")").padStart(24)}`
  );
}
aile         100     200     300     400     500     600     700     800
basari      1.13    1.30    1.52    1.86    2.25    3.44    5.31    8.67
uyari       1.11    1.24    1.41    1.65    1.92    2.96    4.68    7.78
hata        1.26    1.59    2.17    3.20    4.54    6.51    9.07   12.72
bilgi       1.23    1.53    2.06    3.05    4.59    6.50    9.05   12.68

aile    metin basamagi (>=4.5:1)  dolgu basamagi (>=3.0:1)
basari             700 (#247a48)            600 (#2f9d5d)
uyari              700 (#986c06)            700 (#986c06)
hata               500 (#d64029)            400 (#e06e5c)
bilgi              500 (#3075cf)            400 (#6296da)

Tablo, işlevsel renklerde en sık yapılan kurulum hatasını ölçüyor. “Her ailede 500 basamağı metin rengi olsun” biçiminde bir kural konursa hata ve bilgi ailelerinde eşik tutturulur, başarı ve uyarı ailelerinde tutturulmaz: başarı 500 basamağında 2.25, uyarı 1.92 kontrast üretiyor. Aradaki fark iki katın üzerinde.

Sarıya yakın tonlar en uç örnek. Uyarı ailesinde dolgu için gereken 3.0 eşiği hiçbir orta basamakta karşılanmıyor; ilk geçen basamak 700, yani metin basamağıyla aynı. Bu ailede dolgu ile metin arasında bir basamak farkı yaratmak, ailenin daha koyu ucuna inmeyi gerektirir.

Bunun nedeni bağıl parlaklık formülünün kanal katsayılarıdır. Yeşil kanalın katsayısı 0.7152, kırmızının 0.2126, mavinin 0.0722. Sarı ton hem kırmızı hem yeşil kanalı yüksek tuttuğu için parlaklığı hızla yükselir; mavi ağırlıklı bir ton ise aynı açıklık değerinde çok daha karanlık kalır. Basamak numarası bir kontrast sözü vermez; her aile kendi eşiğini kendi hesabıyla bulur.

Bildirim Üçlüsü Kendi Zeminine Karşı Denetlenir

Bir bildirim kutusu üç renk taşır: soluk bir zemin, bir kenarlık ve zemin üzerine düşen metin. Önceki bölümde bulunan metin basamakları beyaza karşı hesaplanmıştı; bildirim kutusunun zemini ise beyaz değil, ailenin 100 basamağıdır.

// bildirim.mjs — islevsel renklerin bildirim uclusu ve ton kanali olmadan ayrilabilirligi

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("");
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 = { 100: 92, 200: 84, 300: 74, 400: 62, 500: 50, 600: 40, 700: 31, 800: 22, 900: 14 };
const AILE = { basari: [145, 54], uyari: [42, 92], hata: [8, 68], bilgi: [214, 62] };
const renk = (aile, b) => hslRgb(AILE[aile][0], AILE[aile][1], ACIKLIK[b]);
const YUZEY = [255, 255, 255];

// Bildirim uclusu: soluk zemin (100), kenarlik (?) ve metin (aileye gore secilen basamak)
const METIN_BASAMAK = { basari: 700, uyari: 700, hata: 500, bilgi: 500 };

console.log("aile    zemin      metin      metin/zemin  esik 4.5  kenarlik  kenarlik/yuzey  esik 3.0");
for (const aile of Object.keys(AILE)) {
  const zemin = renk(aile, 100);
  const metin = renk(aile, METIN_BASAMAK[aile]);
  const kenar = renk(aile, 200);
  const km = kontrast(metin, zemin);
  const kk = kontrast(kenar, YUZEY);
  console.log(
    `${aile.padEnd(7)} ${onaltilik(zemin)}  ${onaltilik(metin)}  ${km.toFixed(2).padStart(9)}:1  ` +
      `${(km >= 4.5 ? "gecti" : "KALDI").padStart(7)}  ${onaltilik(kenar)}  ${kk.toFixed(2).padStart(12)}:1  ${kk >= 3.0 ? "gecti" : "KALDI"}`
  );
}

// Ton kanali disarida birakildiginda dort anlam birbirinden ayrilabiliyor mu?
console.log("\nikili                 kontrast  ton kanali olmadan");
const adlar = Object.keys(AILE);
for (let i = 0; i < adlar.length; i++) {
  for (let j = i + 1; j < adlar.length; j++) {
    const a = renk(adlar[i], METIN_BASAMAK[adlar[i]]);
    const b = renk(adlar[j], METIN_BASAMAK[adlar[j]]);
    const k = kontrast(a, b);
    console.log(
      `${(adlar[i] + " - " + adlar[j]).padEnd(21)} ${k.toFixed(3).padStart(7)}:1  ${k >= 1.5 ? "ayrilir" : "AYRILMAZ"}`
    );
  }
}

// Bilgi rolu ile birincil eylem rengi ayni tonda kuruldu; birbirine karisiyorlar mi?
const eylemAna = hslRgb(214, 62, 40);
const bilgiMetin = renk("bilgi", 500);
console.log(
  `\neylem-ana (${onaltilik(eylemAna)}) ile bilgi metni (${onaltilik(bilgiMetin)}) kontrasti: ${kontrast(eylemAna, bilgiMetin).toFixed(3)}:1`
);

// Duzeltme: metin ve kenarlik basamaklari gercek zeminlerine karsi secilir.
console.log("\naile    metin basamagi (soluk zemine karsi >=4.5)  kenarlik basamagi (yuzeye karsi >=3.0)");
for (const aile of Object.keys(AILE)) {
  const zemin = renk(aile, 100);
  const bas = Object.keys(ACIKLIK).map(Number);
  const m = bas.find((b) => kontrast(renk(aile, b), zemin) >= 4.5);
  const k = bas.find((b) => kontrast(renk(aile, b), YUZEY) >= 3.0);
  console.log(
    `${aile.padEnd(7)} ${(m + " (" + onaltilik(renk(aile, m)) + ", " + kontrast(renk(aile, m), zemin).toFixed(2) + ":1)").padStart(42)}` +
      ` ${(k + " (" + onaltilik(renk(aile, k)) + ", " + kontrast(renk(aile, k), YUZEY).toFixed(2) + ":1)").padStart(37)}`
  );
}
aile    zemin      metin      metin/zemin  esik 4.5  kenarlik  kenarlik/yuzey  esik 3.0
basari  #e0f6e9  #247a48       4.69:1    gecti  #c0ecd3          1.30:1  KALDI
uyari   #fdf2d8  #986c06       4.21:1    KALDI  #fce5b1          1.24:1  KALDI
hata    #f8e0dd  #d64029       3.61:1    KALDI  #f2c2ba          1.59:1  KALDI
bilgi   #dee9f7  #3075cf       3.74:1    KALDI  #bdd3ef          1.53:1  KALDI

ikili                 kontrast  ton kanali olmadan
basari - uyari          1.134:1  AYRILMAZ
basari - hata           1.170:1  AYRILMAZ
basari - bilgi          1.157:1  AYRILMAZ
uyari - hata            1.032:1  AYRILMAZ
uyari - bilgi           1.020:1  AYRILMAZ
hata - bilgi            1.012:1  AYRILMAZ

eylem-ana (#275ea5) ile bilgi metni (#3075cf) kontrasti: 1.415:1

aile    metin basamagi (soluk zemine karsi >=4.5)  kenarlik basamagi (yuzeye karsi >=3.0)
basari                       700 (#247a48, 4.69:1)                 600 (#2f9d5d, 3.44:1)
uyari                        800 (#6c4d04, 6.99:1)                 700 (#986c06, 4.68:1)
hata                         600 (#ab3321, 5.17:1)                 400 (#e06e5c, 3.20:1)
bilgi                        600 (#275ea5, 5.29:1)                 400 (#6296da, 3.05:1)

İlk tablo dört bildirimin üçünde metin eşiğini kaçırıyor. Nedeni bir hesap hatası değil, yanlış zemine karşı hesaplamaktır: basamaklar beyaza karşı seçilmişti, ama metin beyazın üzerine değil ailenin soluk zeminine düşüyor. Soluk zemin beyazdan biraz koyu olduğu için metin ile zemin arasındaki oran daralıyor.

Kenarlık sütunu daha keskin bir sonuç veriyor. Dört ailenin de 200 basamağı yüzeye karşı 1.24 ile 1.59 arasında kontrast üretiyor; hiçbiri 3.0 eşiğine yaklaşmıyor. Soluk bir kenarlık, bildirim kutusunun sınırını ilan etme işini yapmıyor.

Son tablo düzeltmeyi veriyor. Metin basamakları kendi zeminlerine karşı yeniden seçildiğinde uyarı 800’e, hata ve bilgi 600’e taşınıyor; başarı 700’de kalıyor çünkü zaten geçiyordu. Kenarlık basamakları ise ailelere göre 400 ile 700 arasında dağılıyor — dört aile için tek bir basamak numarası vermek yine mümkün değil.

Kural olarak yazılırsa: her kontrast hesabı, rengin gerçekten üzerine düşeceği zeminle yapılır. Beyaza karşı hesaplanmış bir değer, beyaz olmayan bir zeminde geçerli değildir.

Renk Tek Kanal Olamaz

İkinci tablo, bu dersin başında konan sözleşme uyarısını sayıya çeviriyor. Dört işlevsel metin renginin ikili kontrast oranları 1.012 ile 1.170 arasında. Hepsi 1’e çok yakın, yani dört renk bağıl parlaklık ekseninde neredeyse aynı noktada duruyor.

Bunun anlamı şudur: ton kanalı devre dışı kaldığında dört anlam tek bir renge iner. Ton kanalının devre dışı kalması istisnai bir durum değildir — renk ayrımı sınırlı olan okur, gri tonlamalı çıktı, güneş altındaki düşük doygunluklu ekran ve yüksek kontrast kipi bunların hepsidir.

Aynı sonuç WCAG 1.4.1 ölçütünde de yazılıdır: renk, bilgi aktarmanın tek görsel yolu olamaz. Ölçüt bir tasarım tavsiyesi değil, bir uygunluk koşuludur.

Katalog arayüzünde bu, her işlevsel durumun renge ek olarak en az bir kanal daha taşıması demektir:

  • Metin. Bildirim, durumu sözcükle söyler. “Ödünç alma tamamlandı” ile “Bu kayıt rafta değil” arasındaki fark renkten okunmaz, metinden okunur.
  • Simge. Her duruma ayrı bir biçim atanır ve biçim renkten bağımsız ayrılır. Simgenin hangi koşullarda anlam taşıdığı, Bileşen Durumları konusundaki Simge Kullanımı dersinin sorusudur.
  • Konum. Alan düzeyindeki hata iletisi alanın hemen altında durur; ilgili alanla arasındaki boşluk, diğer alanlarla arasındaki boşluktan küçüktür.

Bu üç kanaldan en az birinin renkle birlikte bulunması, hem ölçütü karşılar hem de ilk derste konan iki kanal kuralını sürdürür.

Bilgi Rolü ile Eylem Rengi Çakışıyor

Çıktının üçüncü bloğu, sistem kurulurken gözden kaçan bir çakışmayı yakalıyor. Bilgi ailesi ile önceki derste kurulan ana aile aynı tondan üretilmişti. Bilgi metni ile birincil eylem rengi arasındaki kontrast 1.415:1; iki renk birbirinden ayırt edilemeyecek kadar yakın.

Düzeltme tablosu durumu daha da keskinleştiriyor: bilgi metni doğru basamağa taşındığında değeri #275ea5 oluyor — bu, birincil eylem renginin birebir aynısı. Arayüzde tıklanabilir bir eylemle tıklanamaz bir bilgi satırı aynı renkte görünüyor.

Bu, renk sisteminde iki rolün aynı tonu paylaşmasının bedelidir. Üç çözüm vardır ve seçim arayüzün önceliğine göre yapılır.

Birincisi, bilgi ailesini başka bir tona taşımaktır; ancak bilgi anlamının sözleşmesi mavimsi tonlara yerleşmiştir ve tondan çıkmak anlamı zayıflatır.

İkincisi, birincil eylem rengini ayırmaktır. Eylem rengi bir sözleşme taşımıyorsa — katalog arayüzünde taşımıyor — taşınabilir olan odur.

Üçüncüsü, ayrımı renk dışında bir kanala taşımaktır. Birincil eylem dolgulu bir yüzey üzerinde beyaz metin taşır; bilgi satırı düz zeminde renkli metindir. Yüzey kanalı ikisini zaten ayırır ve ton çakışması görünür bir soruna dönüşmez.

Katalog arayüzünde üçüncü yol seçilir, çünkü ayrım hâlihazırda kurulmuştur ve önceki dersin sonucuyla tutarlıdır: eylem hiyerarşisi ton farkıyla değil yüzey farkıyla kurulur. Ancak bu seçim bir koşula bağlıdır ve koşul yazılmalıdır: bilgi rengi hiçbir yerde dolgulu bir yüzey olarak kullanılamaz. Kullanıldığı anda çakışma görünür hâle gelir.

Özet

  • İşlevsel renkler görünüşü değil durumu bildirir; başarı, uyarı, hata ve bilgi dört temel anlamdır ve her biri tek bir değer değil bir aile alır.
  • Anlam ile renk arasındaki ilişki öğrenilmiş bir sözleşmedir; bu yüzden renk ipucu olabilir, tek taşıyıcı olamaz.
  • Basamak numarası kontrast sözü vermez; sarıya yakın tonlar aynı açıklık değerinde çok daha yüksek parlaklık üretir ve eşiği ancak koyu uçta karşılar.
  • Her kontrast hesabı rengin gerçekten üzerine düşeceği zeminle yapılır; beyaza karşı hesaplanmış bir metin rengi soluk bir zeminde eşiği kaçırabilir.
  • Dört işlevsel renk bağıl parlaklık ekseninde birbirine çok yakındır; ton kanalı devre dışı kaldığında hepsi aynı renge iner, bu yüzden metin, simge ya da konum kanallarından en az biri renge eşlik eder.
  • İki rolün aynı tondan üretilmesi çakışma üretir; çakışma ya tonu taşıyarak ya da ayrımı yüzey kanalına devrederek çözülür, ikincisi seçilirse koşulu yazılır.

Sonraki Adım

Son iki ders boyunca 4.5 ve 3.0 sayıları her hesapta göründü ama nereden geldikleri söylenmedi. Bu eşikler kursun keyfî seçtiği sayılar değil; ölçüm yöntemi, kapsamı ve istisnaları tanımlanmış ölçütlerdir. Sonraki ders bu eşiklerin hangi ölçütten geldiğini, büyük metnin neden ayrı bir eşiğe tabi olduğunu, metin dışı ögelerin hangi kurala girdiğini ve bir arayüzün tamamının bu eşiklere karşı nasıl toplu olarak denetlenebileceğini ele alır.

İ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