İçeriğe geç
academia.sh

Ders 07 / 25

Kartlar

Kartın bir bileşen değil bir gruplama düzeni olduğu; üç tıklanabilirlik kalıbının sekme durağı, erişilebilir ad ve iç içe yerleşim bakımından hesaplanması.

İçindekiler

Önceki konu tek tek denetimleri belirtti ve her denetimin sınırı kendi kutusuydu. Sonuç listesinin kart görünümünde bu sınır belirsizleşir. Bir kart görsel, başlık, yazar, yıl, durum rozeti ve iki denetim taşır; tasarım kararı ise kartın tamamının tıklanabilir olmasıdır.

İstek makuldür: kullanıcı kartın herhangi bir yerine tıkladığında kayıt ayrıntısına gitmelidir. Ancak kartın içinde bir düğme de vardır ve iki denetim iç içe geçemez. Bu ders kartı belirtir ve tıklanabilirlik sorununun üç çözümünü sayarak karşılaştırır.

Kart Bir Bileşen Değil, Bir Düzendir

Kartın çözdüğü sorun, türü farklı bilgilerin bir arada taranabilir bir birim olarak sunulmasıdır: bir görsel, bir başlık, iki üstveri satırı ve bir durum. Görsel gruplamanın ölçütü Arayüz Tasarımının Temelleri kursundaki ortak bölge ilkesidir; bir yüzey, içindekileri tek bir birim olarak okutur.

Kart kullanılmayacağı yer de bundan çıkar: bilgiler türdeş ve karşılaştırılabilir olduğunda kart değil tablo kullanılır. Kırk kaydın yayın yılına göre karşılaştırılması gerekiyorsa kart görünümü her değeri farklı bir dikey konuma yerleştirir ve karşılaştırmayı olanaksız kılar.

Yerel Öge Önce

Kartın bir öge karşılığı yoktur, çünkü bir denetim değildir. İşaretlemesi iki karardan oluşur.

Birincisi, kart listesinin bir liste olarak yazılmasıdır. ul ve li ögeleri ağaca liste rolünü ve öge sayısını bildirir; kullanıcı listeye girdiğinde kaç kayıt olduğunu öğrenir. div yığınıyla kurulan bir ızgarada bu sayı hiçbir yerde bulunmaz.

İkincisi, kartın içinde bir başlık ögesinin bulunmasıdır. Başlık, kaydı ana hatta yerleştirir ve başlık listesinden gezinen kullanıcıya kırk kaydı tek tek okumadan geçme olanağı verir. Kart bağımsız olarak dağıtılabilir bir birimse article ögesiyle sarılır ve aria-labelledby ile başlığa bağlanarak ağaçta adlandırılmış bir bölge olur.

Tıklanabilirlik Sorununun Üç Çözümü

A. Kartın tamamı bir bağlantıdır. Görsel de, başlık da, özet de bağlantının içindedir. İçindeki düğme de öyle.

B. Yalnız başlık bağlantıdır, yüzey ona yayılır. Bağlantı sözde bir elemanla kartın tamamını kaplar; düğme sunum katmanında bu kaplamanın üstüne çıkarılır.

C. Her parça ayrı bağlantıdır. Görsel, başlık ve “Ayrıntı” bağlantısı aynı hedefe gider.

// kart.mjs — kart kaliplarinda sekme duragi sayisi, erisilebilir ad ve ic ice yerlesim

const KART = {
  baslik: "Yapıların Dili",
  yazar: "T. Aksoy",
  yil: "2019",
  durum: "Rafta",
  ozet: "Yapı çözümlemesine giriş niteliğinde bir el kitabı.",
};

// Dugum: { oge, oz, cocuklar }  — metin dugumleri dizgi olarak durur
const etkilesimli = (d) =>
  (d.oge === "a" && d.oz?.href !== undefined) || d.oge === "button";

function odakDuraklari(d, yol = []) {
  if (typeof d === "string") return [];
  const bu = etkilesimli(d) ? [[...yol, d.oge + (d.oz?.href ? "[href]" : "")].join(" > ")] : [];
  const alt = (d.cocuklar ?? []).flatMap((c) => odakDuraklari(c, etkilesimli(d) ? [...yol, d.oge] : yol));
  return [...bu, ...alt];
}

function metin(d) {
  if (typeof d === "string") return d;
  const o = d.oz ?? {};
  if (o["aria-hidden"]) return "";
  if (d.oge === "img") return o.alt ?? "";
  return (d.cocuklar ?? []).map(metin).join(" ").replace(/\s+/g, " ").trim();
}

// Etkilesimli ogenin erisilebilir adi: aria-label > icerik metni
function ad(d) {
  const o = d.oz ?? {};
  if (o["aria-label"]) return o["aria-label"];
  return metin(d);
}

// Ic ice etkilesimli oge sayisi (bir baglantinin icindeki dugme/baglanti)
function icIce(d, icerideyiz = false) {
  if (typeof d === "string") return 0;
  const bu = icerideyiz && etkilesimli(d) ? 1 : 0;
  return bu + (d.cocuklar ?? []).reduce(
    (t, c) => t + icIce(c, icerideyiz || etkilesimli(d)), 0);
}

const govde = () => [
  { oge: "img", oz: { alt: "" } },
  { oge: "h3", cocuklar: [KART.baslik] },
  { oge: "p", cocuklar: [KART.yazar + ", " + KART.yil] },
  { oge: "p", cocuklar: [KART.ozet] },
  { oge: "span", cocuklar: [KART.durum] },
];

// A: kartin tamami tek baglanti; icinde ayrica bir dugme var
const A = {
  oge: "article",
  cocuklar: [
    { oge: "a", oz: { href: "/kayit/K-118" },
      cocuklar: [...govde(), { oge: "button", cocuklar: ["Ödünç al"] }] },
  ],
};

// B: yalnizca baslik baglanti; dugme kardes; kart yuzeyi baglantiya yayiliyor
const B = {
  oge: "article",
  cocuklar: [
    { oge: "img", oz: { alt: "" } },
    { oge: "h3", cocuklar: [{ oge: "a", oz: { href: "/kayit/K-118" }, cocuklar: [KART.baslik] }] },
    { oge: "p", cocuklar: [KART.yazar + ", " + KART.yil] },
    { oge: "p", cocuklar: [KART.ozet] },
    { oge: "span", cocuklar: [KART.durum] },
    { oge: "button", cocuklar: ["Ödünç al"] },
  ],
};

// C: gorsel, baslik ve "Ayrinti" ayri ayri baglanti
const C = {
  oge: "article",
  cocuklar: [
    { oge: "a", oz: { href: "/kayit/K-118" }, cocuklar: [{ oge: "img", oz: { alt: "" } }] },
    { oge: "h3", cocuklar: [{ oge: "a", oz: { href: "/kayit/K-118" }, cocuklar: [KART.baslik] }] },
    { oge: "p", cocuklar: [KART.yazar + ", " + KART.yil] },
    { oge: "span", cocuklar: [KART.durum] },
    { oge: "a", oz: { href: "/kayit/K-118" }, cocuklar: ["Ayrıntı"] },
    { oge: "button", cocuklar: ["Ödünç al"] },
  ],
};

const KALIPLAR = [
  ["A: kartin tamami baglanti", A],
  ["B: baslik baglanti + yayilan yuzey", B],
  ["C: her parca ayri baglanti", C],
];

const SAYFADAKI_KART = 40;

console.log("kalip".padEnd(36) + "durak".padEnd(8) + "40 kartta".padEnd(11) +
  "ic ice".padEnd(8) + "ilk duragin adi (karakter)");
for (const [baslik, k] of KALIPLAR) {
  const duraklar = odakDuraklari(k);
  const ilk = (k.cocuklar ?? []).flatMap(function bul(d) {
    if (typeof d === "string") return [];
    return etkilesimli(d) ? [d] : (d.cocuklar ?? []).flatMap(bul);
  })[0];
  const a = ad(ilk);
  console.log(baslik.padEnd(36) + String(duraklar.length).padEnd(8) +
    String(duraklar.length * SAYFADAKI_KART).padEnd(11) +
    String(icIce(k)).padEnd(8) + `"${a.slice(0, 34)}${a.length > 34 ? "" : ""}" (${a.length})`);
}

console.log("\nsekme duraklari:");
for (const [baslik, k] of KALIPLAR) {
  console.log("  " + baslik);
  for (const d of odakDuraklari(k)) console.log("    " + d);
}

console.log("\nkural denetimi:");
let bulgu = 0;
for (const [baslik, k] of KALIPLAR) {
  const duraklar = odakDuraklari(k);
  const ogeler = (k.cocuklar ?? []).flatMap(function bul(d) {
    if (typeof d === "string") return [];
    return etkilesimli(d) ? [d, ...(d.cocuklar ?? []).flatMap(bul)] : (d.cocuklar ?? []).flatMap(bul);
  });
  const adlar = ogeler.map(ad);
  const b = [];
  if (icIce(k) > 0) b.push(`etkilesimli oge baska bir etkilesimli ogenin icinde (${icIce(k)} adet)`);
  for (const a of adlar) {
    if (a === "") b.push("adsiz durak: icerigi bos alternatif metinli gorsel");
    else if (a.length > 60) b.push(`erisilebilir ad ${a.length} karakter: gezinme listesinde okunamaz`);
  }
  const hedefler = new Map();
  for (const o of ogeler) {
    const h = o.oz?.href;
    if (!h) continue;
    if (!hedefler.has(h)) hedefler.set(h, new Set());
    hedefler.get(h).add(ad(o));
  }
  for (const [h, kume] of hedefler) {
    if (kume.size > 1) b.push(`ayni hedefe (${h}) farkli adlarla ${kume.size} baglanti gidiyor`);
  }
  if (duraklar.length * SAYFADAKI_KART > 100)
    b.push(`40 kartta ${duraklar.length * SAYFADAKI_KART} durak: liste sekme tusuyla gecilemez`);
  for (const x of b) { bulgu++; console.log("  " + baslik.padEnd(36) + x); }
}
console.log(`\n${KALIPLAR.length} kalipta ${bulgu} bulgu`);
kalip                               durak   40 kartta  ic ice  ilk duragin adi (karakter)
A: kartin tamami baglanti           2       80         1       "Yapıların Dili T. Aksoy, 2019 Yapı…" (96)
B: baslik baglanti + yayilan yuzey  2       80         0       "Yapıların Dili" (14)
C: her parca ayri baglanti          4       160        0       "" (0)

sekme duraklari:
  A: kartin tamami baglanti
    a[href]
    a > button
  B: baslik baglanti + yayilan yuzey
    a[href]
    button
  C: her parca ayri baglanti
    a[href]
    a[href]
    a[href]
    button

kural denetimi:
  A: kartin tamami baglanti           etkilesimli oge baska bir etkilesimli ogenin icinde (1 adet)
  A: kartin tamami baglanti           erisilebilir ad 96 karakter: gezinme listesinde okunamaz
  C: her parca ayri baglanti          adsiz durak: icerigi bos alternatif metinli gorsel
  C: her parca ayri baglanti          ayni hedefe (/kayit/K-118) farkli adlarla 3 baglanti gidiyor
  C: her parca ayri baglanti          40 kartta 160 durak: liste sekme tusuyla gecilemez

3 kalipta 5 bulgu

Bulguların Okunması

A kalıbı iki ayrı kural kırar. Bağlantının içindeki düğme, ağaçta bir etkileşimli ögenin içinde ikinci bir etkileşimli öge olarak durur; bu yerleşim tanımlı değildir ve gerçekleştirimlerin nasıl davranacağı öngörülemez. İkincisi ölçülebilir: bağlantının adı doksan altı karakter uzunluğunda. Kullanıcı bağlantı listesini açtığında her satır bir paragraf uzunluğundadır ve liste taranamaz hâle gelir.

C kalıbı sayıyla düşer. Kart başına dört durak, kırk kartlık bir listede yüz altmış durak eder. Klavyeyle gezen bir kullanıcının listeyi geçmesi yüz altmış sekme vuruşu gerektirir. Aynı kalıp iki bulgu daha üretiyor: alternatif metni boş görselden oluşan bağlantı adsız kalıyor ve aynı hedefe üç farklı adla üç bağlantı gidiyor.

B kalıbı denetimden temiz çıkıyor. Kart başına iki durak, tek ve kısa bir bağlantı adı, iç içe yerleşim yok. Bunun bedeli sunum katmanındadır ve belirtimde yazılmalıdır: yayılan yüzey kartın metnini fare ile seçilemez hâle getirir. Düzeltmesi, kaplamanın işaret olaylarını yalnızca boş alanlarda yakalamasıdır.

Sekme durağı listesi bir ayrıntıyı daha gösteriyor. B ve C kalıplarında düğme kartın kendi düzeyindedir; A kalıbında a > button yolunda, yani bağlantının içindedir. Yol bilgisi, işaretlemeye bakmadan iç içe yerleşimi saptamanın yoludur.

Klavye Sözleşmesi

Kartın kendi klavye sözleşmesi yoktur. Kart odak almaz, tuş dinlemez ve sekme sırasında görünmez. Geçerli olan sözleşmeler, içindeki bağlantı ve düğmenin sözleşmeleridir.

Bu, belirtimin bir kararıdır: karta tabindex yazıp tıklama dinleyicisi bağlamak, sekme durağı sayısını kart başına bir artırır ve odak alan bir kabuk üretir. O kabuğun rolü yoktur, adı belirsizdir ve giriş tuşuna verdiği yanıt elle yazılmak zorundadır.

Kart listesi uzunsa sekme durağı sayısı ayrı bir kararla düşürülür: listeden önce bir atlama bağlantısı konur ve kullanıcıya listeyi geçme yolu verilir. Kart içindeki ikincil denetimleri gizleyip yalnız üzerine gelindiğinde göstermek bu sayıyı düşürmez, çünkü gizlenen denetim klavyeyle ulaşılamaz hâle gelir.

Ölçülebilir Kısıtlar

2.4.7 Odak Görünürlüğü. Kartın kenarında taşan içeriği kırpan bir sunum kararı, kart kenarına yakın bir denetimin odak halkasını da kırpar. Gösterge var olur, görünmez. Saptaması bir klavye turudur.

1.4.11 Metin Dışı Kontrast. Kartı sayfa yüzeyinden ayıran sınır, bilgi taşıyorsa 3:1 ölçüsüne girer. Kartlar birbirinden yalnız gölgeyle ayrılıyorsa ve gölge düşük karşıtlıksa, grup sınırı görülemez.

1.4.1 Rengin Tek Taşıyıcı Olmaması. “Rafta”, “Ödünçte”, “Ayırtılmış” durum rozetleri yalnız renkle ayrılamaz; her rozet metnini taşır.

1.4.10 Yeniden Akış. Kart ızgarası, 320 CSS pikseli genişliğe indirildiğinde iki yönlü kaydırma gerektirmemelidir. Sabit genişlikli kart kutuları bu ölçütü doğrudan kaldırır.

2.5.8 Hedef Boyutu. Karttaki ikincil denetimler — işaretleme ve paylaşma düğmeleri — 24 piksel ölçüsüne ya da aralık istisnasına uymalıdır.

Sık Yapılan Hata

Kartın başlıksız bırakılması. Kalın yazılmış bir metin başlık değildir. Saptaması, kart listesindeki başlık düzeylerinin sayılmasıdır: kırk kart varsa ana hatta kırk başlık bulunmalıdır.

Görselin kayıt adıyla adlandırılması. Kartın görseli, yanında zaten başlık dururken alternatif metin taşırsa aynı bilgi iki kez duyurulur. Kararı: kart içindeki kapak görseli sunumsaldır ve alternatif metni boştur.

Kart listesinin liste olarak yazılmaması. Saptaması, listenin ağaçtaki öge sayısının ekrandaki kart sayısıyla karşılaştırılmasıdır; sayı yoksa yapı yoktur.

Özet

  • Kart bir denetim değil, türü farklı bilgileri tek birim olarak sunan bir gruplama düzenidir; türdeş ve karşılaştırılabilir veri için tablo kullanılır.
  • Kart listesi liste ögeleriyle yazılır ve her kart bir başlık ögesi taşır; bu ikisi öge sayısını ve ana hattı ağaca bildirir.
  • Kartın tamamını bağlantıya almak iç içe etkileşimli öge üretir ve bağlantı adını okunamaz uzunluğa çıkarır.
  • Her parçayı ayrı bağlantı yapmak sekme durağı sayısını kart başına dörde çıkarır; kırk kartlık listede yüz altmış durak eder.
  • Yalnız başlığın bağlantı olduğu ve yüzeyin ona yayıldığı kalıp üç ölçütten de temiz çıkar; bedeli metnin seçilebilirliğidir ve bu, kaplamanın olay yakalama alanıyla çözülür.
  • Kartın kendi klavye sözleşmesi yoktur; karta odak vermek rolsüz ve adsız bir kabuk üretir.

Sonraki Adım

Kart, içindeki denetimlerin toplamıydı; kendisi bir davranış taşımıyordu. Kayıt ayrıntısı sayfasında ise durum değişir: künye, mevcut kopyalar ve ödünç geçmişi üç ayrı bölümdür ve aynı anda yalnız biri gösterilir. Bu, kapsayıcının kendisinin bir davranış taşıdığı ilk kalıptır — hangi bölümün görüneceğine o karar verir. Sonraki ders sekme kalıbını belirtir: sekme başlıklarıyla panellerin nasıl ilişkilendirildiğini, dönen sekme durağının burada neden zorunlu olduğunu ve seçimin kendiliğinden mi yoksa elle mi etkinleştirileceği kararını 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