İçeriğe geç
academia.sh

Ders 21 / 22

Simge Kullanımı

Anlam taşıyan ve süsleyici simge ayrımı, erişilebilir adın küçük bir modelle hesaplanması, simge kümesinin çokanlamlılık denetimi ve ölçek ile çizgi kalınlığının tutarlılığı.

İçindekiler

Önceki ders hata iletisinin yanına konan simgeyi gerekçelendirmeden bıraktı. İşlevsel Renkler dersinde simge, renge eşlik edecek ikinci kanal olarak sayılmıştı; ancak simgenin kendisinin ne zaman bilgi taşıdığı, ne zaman yalnızca yer kapladığı sorulmadı.

Bu ders simgeyi tek bir ayrım üzerinden ele alır: simge bir bilgi taşıyor mu, yoksa yanındaki metnin taşıdığı bilgiyi görsel olarak destekliyor mu? Ayrım estetik değildir; işaretlemede farklı sonuçlar üretir ve yanlış tarafa düşen bir simge ya bilgiyi tümüyle kaybettirir ya da aynı bilgiyi iki kez okutur.

İki Sınıf, İki Farklı Sonuç

Anlam taşıyan simge (semantic icon), tek başına bir bilgi ya da eylem bildirir. Yanında metin yoktur. Katalog arayüzünde arama alanının sonundaki büyüteç, kayıt satırının sonundaki kaldırma simgesi bu sınıfa girer. Bu simgelerin bir adı olmak zorundadır; adsız bir simge düğmesi, klavye ve ekran okuyucu için varlığı olmayan bir düğmedir.

Süsleyici simge (decorative icon), yanındaki metnin bildirdiği şeyi görsel olarak tekrar eder. Hata iletisinin başındaki ünlem simgesi bu sınıfa girer; ileti zaten “ISBN numarası doğrulanamadı” diyor. Bu simgelerin adı olmamalıdır; olursa aynı bilgi iki kez duyurulur.

Ayrımın sınavı basit bir soruyla yapılır: simge kaldırılırsa bilgi kaybolur mu? Kaybolursa anlam taşıyor, kaybolmuyorsa süslüyor. Aynı simge iki yerde iki farklı sınıfa düşebilir; büyüteç, yalnız durduğunda anlam taşır, “Ara” yazan bir düğmenin içinde süsleyicidir.

Erişilebilir Ad Hesaplanabilir

Bir ögenin erişilebilir adı, tanımlı bir sıra izlenerek türetilir. Sıra kısaca şudur: aria-labelledby başvurusu, sonra aria-label değeri, sonra ögenin içeriğinden üretilen metin, en son title özniteliği. Bu sıra bir modelle canlandırılabilir ve simge kurulumları tek tek denetlenebilir.

// erisilebilir-ad.mjs — simge tasiyan dugmelerin erisilebilir adinin hesaplanmasi

// Kucuk bir agac modeli: her dugum bir etiket, oznitelik kumesi, metin ve cocuklar tasir.
const dugum = (etiket, oznitelik = {}, cocuklar = [], metin = "") => ({ etiket, oznitelik, cocuklar, metin });

// Icerikten ad uretebilen etiketler
const ICERIKTEN_AD = new Set(["button", "a", "th", "label", "legend", "summary"]);

function metinTopla(d, ref) {
  if (d.oznitelik["aria-hidden"] === "true") return "";
  if (d.etiket === "svg") {
    if (d.oznitelik.role === "img") {
      const baslik = d.cocuklar.find((c) => c.etiket === "title");
      return baslik ? baslik.metin : "";
    }
    return "";
  }
  if (d.etiket === "img") return d.oznitelik.alt ?? "";
  const parcalar = [d.metin, ...d.cocuklar.map((c) => metinTopla(c, ref))];
  return parcalar.filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
}

function erisilebilirAd(d, sozluk = {}) {
  // 1) aria-labelledby
  if (d.oznitelik["aria-labelledby"]) {
    const adlar = d.oznitelik["aria-labelledby"]
      .split(/\s+/)
      .map((id) => (sozluk[id] ? metinTopla(sozluk[id], sozluk) : ""))
      .filter(Boolean);
    if (adlar.length) return { ad: adlar.join(" "), kaynak: "aria-labelledby" };
  }
  // 2) aria-label
  if (d.oznitelik["aria-label"] && d.oznitelik["aria-label"].trim()) {
    return { ad: d.oznitelik["aria-label"].trim(), kaynak: "aria-label" };
  }
  // 3) icerikten ad
  if (ICERIKTEN_AD.has(d.etiket)) {
    const m = metinTopla(d, sozluk);
    if (m) return { ad: m, kaynak: "icerik" };
  }
  // 4) title oznitelii (son care)
  if (d.oznitelik.title && d.oznitelik.title.trim()) {
    return { ad: d.oznitelik.title.trim(), kaynak: "title (son care)" };
  }
  return { ad: "", kaynak: "yok" };
}

const simge = (oznitelik = {}, cocuklar = []) => dugum("svg", oznitelik, cocuklar);
const baslik = (metin) => dugum("title", {}, [], metin);

const ORNEKLER = [
  ["yalin simge", dugum("button", {}, [simge()])],
  ["aria-label + gizli simge", dugum("button", { "aria-label": "Odunc al" }, [simge({ "aria-hidden": "true" })])],
  ["role=img + title", dugum("button", {}, [simge({ role: "img" }, [baslik("Odunc al")])])],
  ["gizli simge + metin", dugum("button", {}, [simge({ "aria-hidden": "true" }), dugum("span", {}, [], "Odunc al")])],
  ["adlandirilmis simge + metin", dugum("button", {}, [simge({ role: "img" }, [baslik("Odunc al")]), dugum("span", {}, [], "Odunc al")])],
  ["yalnizca title", dugum("button", { title: "Odunc al" }, [simge()])],
];

console.log("kurulum                       erisilebilir ad        kaynak            sonuc");
for (const [ad, agac] of ORNEKLER) {
  const s = erisilebilirAd(agac);
  let sonuc;
  if (!s.ad) sonuc = "ADSIZ";
  else if (/^(.+) \1$/.test(s.ad)) sonuc = "YINELENMIS";
  else if (s.kaynak.startsWith("title")) sonuc = "zayif";
  else sonuc = "gecerli";
  console.log(`${ad.padEnd(29)} ${JSON.stringify(s.ad).padEnd(22)} ${s.kaynak.padEnd(17)} ${sonuc}`);
}
kurulum                       erisilebilir ad        kaynak            sonuc
yalin simge                   ""                     yok               ADSIZ
aria-label + gizli simge      "Odunc al"             aria-label        gecerli
role=img + title              "Odunc al"             icerik            gecerli
gizli simge + metin           "Odunc al"             icerik            gecerli
adlandirilmis simge + metin   "Odunc al Odunc al"    icerik            YINELENMIS
yalnizca title                "Odunc al"             title (son care)  zayif

İlk satır en sık yapılan hatadır: simge düğmenin içine konur, başka hiçbir şey yazılmaz. Hesaplanan ad boş; düğme ekranda görünür ama adı yoktur. Klavyeyle gezen kullanıcı düğmeye odaklandığında ne olduğunu bilemez.

Beşinci satır ters yöndeki hatadır. Simge hem adlandırılmış hem de yanında metin var; ad “Odunc al Odunc al” olarak hesaplanıyor. Anlam taşıyan bir simgenin süsleyici olması gereken yerde adlandırılması bu sonucu verir.

Altıncı satır çalışıyor ama zayıf. title özniteliği ad zincirinin son halkasıdır ve yalnızca imleç üzerinde durduğunda görünür; dokunmatik girdide hiç görünmez, klavyeyle erişimde güvenilir değildir. Adı title ile vermek, adı vermemekle vermek arasında bir yerdedir.

İkinci, üçüncü ve dördüncü satırlar geçerli kurulumlardır ve aralarındaki seçim duruma bağlıdır. Simge yalnız duruyorsa ikinci ya da üçüncü; simgenin yanında metin varsa dördüncü, yani simge gizlenir ve ad metinden gelir.

Simge Kümesi Kendi İçinde Tutarlı Olmalı

Tek tek simgeler doğru işaretlense bile küme düzeyinde bir tutarlılık sorunu kalabilir: aynı biçimin iki farklı anlamı taşıması ya da birbirine yakın iki anlamın aynı biçimle çizilmesi.

// simge-kumesi.mjs — simge kumesinin tutarlilik denetimi ve olcek/cizgi kalinligi hesabi

// Katalog arayuzunde kullanilan simgelerin anlam-bicim eslesmesi
const KUME = [
  { anlam: "arama",            bicim: "buyutec" },
  { anlam: "filtre",           bicim: "huni" },
  { anlam: "odunc al",         bicim: "asagi-ok-kutu" },
  { anlam: "indir",            bicim: "asagi-ok-kutu" },
  { anlam: "kaldir",           bicim: "cop-kutusu" },
  { anlam: "sil",              bicim: "carpi" },
  { anlam: "kapat",            bicim: "carpi" },
  { anlam: "listeye ekle",     bicim: "arti" },
  { anlam: "yeni kayit",       bicim: "arti-daire" },
  { anlam: "hata",             bicim: "unlem-daire" },
  { anlam: "uyari",            bicim: "unlem-ucgen" },
  { anlam: "bilgi",            bicim: "i-daire" },
  { anlam: "basari",           bicim: "onay-daire" },
];

// 1) Ayni bicim birden fazla anlam tasiyor mu?
const bicimden = {};
for (const s of KUME) (bicimden[s.bicim] ??= []).push(s.anlam);
console.log("bicim            anlamlar                        durum");
for (const [b, anlamlar] of Object.entries(bicimden)) {
  console.log(
    `${b.padEnd(16)} ${anlamlar.join(", ").padEnd(31)} ${anlamlar.length > 1 ? "COKANLAMLI" : "tekil"}`
  );
}

// 2) Yakin anlamlar farkli bicimlerle mi cizilmis?
const YAKIN = [["kaldir", "sil"], ["odunc al", "indir"], ["listeye ekle", "yeni kayit"]];
console.log("\nyakin anlam cifti           bicimler                         durum");
for (const [a, b] of YAKIN) {
  const ba = KUME.find((s) => s.anlam === a).bicim;
  const bb = KUME.find((s) => s.anlam === b).bicim;
  console.log(
    `${(a + " / " + b).padEnd(27)} ${(ba + " / " + bb).padEnd(32)} ${ba === bb ? "AYRILMIYOR" : "ayriliyor"}`
  );
}

// 3) Olcek: kutu buyudukce cizgi kalinligi orantili buyurse gorsel agirlik korunur mu?
const TABAN_KUTU = 24, TABAN_CIZGI = 1.5;
console.log("\nkutu  orantili cizgi  yuvarlanmis  cizgi/kutu  taban orana gore sapma");
for (const kutu of [14, 16, 18, 20, 24, 28, 32]) {
  const orantili = (TABAN_CIZGI * kutu) / TABAN_KUTU;
  const yuvarlanmis = Math.round(orantili * 2) / 2; // yarim piksel adimlarina yuvarla
  const oran = yuvarlanmis / kutu;
  const taban = TABAN_CIZGI / TABAN_KUTU;
  console.log(
    `${String(kutu).padStart(4)} ${orantili.toFixed(3).padStart(15)} ${yuvarlanmis.toFixed(2).padStart(12)} ${oran.toFixed(4).padStart(11)} ${((oran / taban - 1) * 100).toFixed(2).padStart(22)}%`
  );
}

// 4) Simge dugmesinin dokunma hedefi: 24 px simge, 44 px hedef
const SIMGE = 24, HEDEF = 44;
console.log(`\nsimge ${SIMGE} px, dokunma hedefi ${HEDEF} px -> her kenarda dolgu: ${(HEDEF - SIMGE) / 2} px`);
console.log(`simge alaninin hedefe orani: ${((SIMGE * SIMGE) / (HEDEF * HEDEF) * 100).toFixed(1)}%`);

// 5) Simge cizgisinin yuzeyden ayrilmasi: metin disi kontrast esigi 3.0
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 coz = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
console.log("\nsimge rengi  yuzeye karsi  esik 3.0");
for (const hex of ["#b7bcc2", "#969da6", "#757e8a", "#5e656e"]) {
  const k = kontrast(coz(hex), [255, 255, 255]);
  console.log(`${hex}      ${k.toFixed(2).padStart(8)}:1  ${k >= 3 ? "gecti" : "KALDI"}`);
}
bicim            anlamlar                        durum
buyutec          arama                           tekil
huni             filtre                          tekil
asagi-ok-kutu    odunc al, indir                 COKANLAMLI
cop-kutusu       kaldir                          tekil
carpi            sil, kapat                      COKANLAMLI
arti             listeye ekle                    tekil
arti-daire       yeni kayit                      tekil
unlem-daire      hata                            tekil
unlem-ucgen      uyari                           tekil
i-daire          bilgi                           tekil
onay-daire       basari                          tekil

yakin anlam cifti           bicimler                         durum
kaldir / sil                cop-kutusu / carpi               ayriliyor
odunc al / indir            asagi-ok-kutu / asagi-ok-kutu    AYRILMIYOR
listeye ekle / yeni kayit   arti / arti-daire                ayriliyor

kutu  orantili cizgi  yuvarlanmis  cizgi/kutu  taban orana gore sapma
  14           0.875         1.00      0.0714                  14.29%
  16           1.000         1.00      0.0625                   0.00%
  18           1.125         1.00      0.0556                 -11.11%
  20           1.250         1.50      0.0750                  20.00%
  24           1.500         1.50      0.0625                   0.00%
  28           1.750         2.00      0.0714                  14.29%
  32           2.000         2.00      0.0625                   0.00%

simge 24 px, dokunma hedefi 44 px -> her kenarda dolgu: 10 px
simge alaninin hedefe orani: 29.8%

simge rengi  yuzeye karsi  esik 3.0
#b7bcc2          1.91:1  KALDI
#969da6          2.74:1  KALDI
#757e8a          4.11:1  gecti
#5e656e          5.89:1  gecti

İlk tablo iki çokanlamlılık buluyor. “Çarpı” biçimi hem silme hem kapatma anlamını taşıyor; bu ikisi geri alınabilirlik açısından taban tabana zıt eylemlerdir. İkinci çakışma daha ağır: “aşağı ok kutu” biçimi hem ödünç almayı hem indirmeyi gösteriyor.

İkinci tablo çakışmanın hangi durumda gerçekten sorun olduğunu ayırıyor. Kaldırma ile silme yakın anlamlardır ve farklı biçimlerle çizilmiş — sorun yok. Ödünç alma ile indirme ise hem yakın anlamlı hem aynı biçimli; kullanıcı hangi düğmenin kayıt ayrıntısını indirdiğini, hangisinin kitabı ödünç aldığını simgeden okuyamaz.

Çokanlamlılık her zaman kusur değildir. Çarpı biçiminin iki anlamı, göründükleri bağlamla ayrılır: kapatma çarpısı bir katmanın köşesinde, silme çarpısı bir liste ögesinin yanında durur. Bağlam ayrımı üretiyorsa çakışma kabul edilebilir. Ödünç alma ile indirme aynı satırda yan yana durduğu için bağlam ayrım üretmiyor; orada biçimlerden biri değişmelidir.

Üçüncü tablo ölçek sorununu ölçüyor. Çizgi kalınlığı kutu boyutuyla orantılı büyütülüp yarım piksele yuvarlandığında, çizgi–kutu oranı taban orandan yüzde 11 ile yüzde 20 arasında sapıyor. Sapma yönü de düzensiz: 18 pikselde çizgi inceliyor, 20 pikselde kalınlaşıyor. Aynı kümedeki iki simge yan yana konduğunda biri diğerinden ağır görünür.

Çözüm, tipografik ölçekteki çözümün aynısıdır: ara boyut üretilmez. Simge kümesi yalnızca sapması sıfır olan boyutlarda çizilir — burada 16, 24 ve 32 — ve arada bir boyut gerektiğinde ölçek değil, en yakın basamak seçilir.

Son iki blok iki sınırı veriyor. Yirmi dört piksellik bir simge, kırk dört piksellik dokunma hedefinin yalnızca yüzde 29.8’ini kaplıyor; geri kalanı görünmez dolgudur ve tıklanabilir alanın simgeden büyük olması gerektiğini hatırlatır. Simge çizgisinin rengi ise metin dışı kontrast eşiğine tabidir: nötr 300 ve 400 basamakları eşiği geçmiyor, simge en açık nötr 500 basamağında çizilebilir.

Simge Metnin Yerini Tutmaz

Yaygın bir tasarım tercihi, yer kazanmak için metin etiketlerini kaldırıp yalnızca simge bırakmaktır. Bu tercih bir bilgiyi öğrenilmiş bir sözleşmeye devreder ve sözleşmenin var olup olmadığı simge simge değişir.

Büyüteç ile arama arasındaki bağ yerleşiktir. Buna karşılık “aşağı ok kutu” ile ödünç alma arasında yerleşik bir bağ yoktur; o bağ bu arayüzün kendi icadıdır ve kullanıcı onu ancak kullanarak öğrenir. Yerleşik olmayan bir bağın metinsiz kullanılması, kullanıcıyı deneme yanılmaya zorlar.

Ölçüt şu biçimde yazılabilir: simge, ancak taşıdığı anlam arayüzün dışında da yerleşikse metinsiz kullanılabilir. Yerleşik değilse simgenin yanında metin durur; simge o zaman süsleyici sınıfa geçer ve gizlenir.

İki durumda metin kaldırılabilir. Birincisi, eylem geri alınabilir ve yanlış tıklamanın bedeli düşükse. İkincisi, aynı eylem arayüzde başka bir yerde metinli olarak da sunuluyorsa; simge o zaman bir kısayoldur, tek yol değildir.

Özet

  • Simgeler anlam taşıyan ve süsleyici olarak ikiye ayrılır; sınav, simge kaldırıldığında bilginin kaybolup kaybolmadığıdır.
  • Anlam taşıyan simgenin bir erişilebilir adı olmak zorundadır; süsleyici simge gizlenmelidir, aksi hâlde aynı bilgi iki kez duyurulur.
  • Erişilebilir ad tanımlı bir sırayla hesaplanır; adı title özniteliğine bırakmak zayıf bir çözümdür.
  • Simge kümesi çokanlamlılık açısından denetlenir; çakışma ancak bağlam ayrım üretiyorsa kabul edilebilir.
  • Çizgi kalınlığı her boyutta orantılı kalmaz; küme yalnızca sapması sıfır olan boyutlarda çizilir ve ara boyut üretilmez.
  • Simge yalnızca taşıdığı anlam arayüzün dışında yerleşikse metinsiz kullanılabilir.

Sonraki Adım

Bu ders simgenin metnin yerini tutmadığını gösterdi ve tartışmayı metne devretti. Arayüzdeki metin — düğme etiketleri, alan adları, boş durum cümleleri, hata iletileri — bugüne kadar her derste geçti ama hiçbirinde tasarım kararı olarak ele alınmadı. Sonraki ders arayüz metnini tasarımın kendisi olarak ele alır: eylem etiketlerinin nasıl seçildiğini, hata iletisinin hangi üç parçadan oluştuğunu ve metnin uzunluğunun düzeni nasıl kısıtladığını hesapla gösterir.

İ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