İçeriğe geç
academia.sh

Ders 19 / 23

Özel Özellikler

Özel özelliklerin bildirildiği ögede hesaplanması, kalıtımın hesaplanmış değeri taşıması, yedek değer zincirleri, hesaplanan değerde geçersizlik ve bileşenin biçem arayüzünün kurulması.

İçindekiler

Önceki dersin bileşen dosyasındaki her kural bir ada başvuruyor: var(--cizgi), var(--uyari), var(--aralik-1). Bu adlar CSS ile Görsel Sunum kursunda kök ögede tanımlandı ve o kurs için yeterliydi.

Bileşen mimarisi daha fazlasını ister. Bir kart varyantı yalnız zemin rengini değiştirmeli; yan bilgi bölümü kendi renk ölçeğini taşımalı; bir bileşen, dışarıdan hangi değerlerin ayarlanabileceğini bildirmeli. Bu ders özel özelliklere mimari bir araç olarak bakar ve önce hesaplanma anını netleştirir.

Hesaplanma Anı

Özel özellik (custom property) iki tire ile başlayan bir addır ve var() ile okunur. Belirleyici kuralı, değerinin bildirildiği ögede hesaplanmasıdır.

Bu, kalıtımın ham metni değil hesaplanmış değeri taşıdığı anlamına gelir. Kök ögede --kart-zemin: var(--zemin) yazıldığında, kökteki --zemin değeri yerine konur ve alt ağaca giden şey artık o renktir. Bir alt ögede --zemin yeniden tanımlansa bile kökte hesaplanmış --kart-zemin değişmez.

Aşağıdaki program bunu bir ağaç üzerinde çözüyor.

// ozelozellik.mjs — ozel ozelliklerin hesaplanma ani, kalitim, yedek deger ve gecersizlik davranisi
// Kural: bir ozel ozelligin degeri, BILDIRILDIGI ogede hesaplanir; alt agaca hesaplanmis
// deger kalitilir. Alt agacta bir kaynak ozelligin yeniden tanimlanmasi, ustte hesaplanmis
// bir tureve geriye donuk etki etmez.
const AGAC = {
  ad: ":root",
  bildirim: { "--zemin": "hsl(203 18% 97%)", "--metin": "hsl(211 29% 16%)",
              "--kart-zemin": "var(--zemin)", "--aralik-1": "1.5rem" },
  cocuk: [{
    ad: ".yan-bilgi",
    bildirim: { "--zemin": "hsl(203 18% 92%)" },            // yalniz --zemin yeniden tanimli
    cocuk: [{
      ad: ".olcum-kart",
      bildirim: {},
      cocuk: [
        { ad: ".olcum-kart--uyari", bildirim: { "--kart-zemin": "hsl(0 66% 96%)" }, cocuk: [] },
        { ad: ".olcum-kart--sade",  bildirim: { "--kart-zemin": "yanlis-deger" },   cocuk: [] },
        { ad: ".olcum-kart--yerel", bildirim: { "--kart-zemin": "var(--zemin)" },   cocuk: [] },
      ],
    }],
  }],
};

const VAR_KALIBI = /var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/;

// metin icindeki var() basvurularini coz; ara(ad) bir ozel ozelligin degerini dondurur
function coz(metin, ara) {
  let s = metin, adim = 0;
  while (VAR_KALIBI.test(s)) {
    if (++adim > 50) return { deger: null, neden: "dongu" };
    const [tam, ad, yedek] = s.match(VAR_KALIBI);
    const bulunan = ara(ad);
    if (bulunan && bulunan.neden) return bulunan;                       // dongu yukari tasinir
    if (!bulunan || bulunan.deger === null) {
      if (yedek === undefined) return { deger: null, neden: `${ad} tanimsiz, yedek yok` };
      s = s.replace(tam, yedek);
    } else {
      s = s.replace(tam, bulunan.deger);
    }
  }
  return { deger: s.trim(), neden: null };
}

// bir ogenin ozel ozellik haritasi: kalitilan harita + kendi bildirimlerinin hesaplanmasi
function harita(dugum, kalitilan) {
  const sonuc = { ...kalitilan };
  const cozuluyor = new Set();
  const ara = (ad) => {
    if (ad in dugum.bildirim) {
      if (cozuluyor.has(ad)) return { deger: null, neden: "dongu" };
      cozuluyor.add(ad);
      const c = coz(dugum.bildirim[ad], ara);
      cozuluyor.delete(ad);
      return c;
    }
    return ad in kalitilan ? { deger: kalitilan[ad], neden: null } : null;
  };
  for (const ad of Object.keys(dugum.bildirim)) {
    const c = ara(ad);
    sonuc[ad] = c.deger;                                   // gecersizse null kalir
  }
  return sonuc;
}

const GECERLI_RENK = /^(hsl\(|rgb\(|#)/;
function gez(dugum, kalitilan = {}, yol = []) {
  const h = harita(dugum, kalitilan);
  const ad = [...yol, dugum.ad];
  const c = coz("var(--kart-zemin, var(--zemin))", (n) => (n in h ? { deger: h[n], neden: null } : null));
  let sonuc;
  if (c.deger === null) sonuc = `GECERSIZ (${c.neden})`;
  else if (!GECERLI_RENK.test(c.deger)) sonuc = `"${c.deger}" -> hesaplanan degerde gecersiz`;
  else sonuc = c.deger;
  console.log("  " + ad.join(" > ").padEnd(62) + sonuc);
  for (const c2 of dugum.cocuk) gez(c2, h, ad);
}

console.log("--- agacta background-color: var(--kart-zemin, var(--zemin)) ---");
gez(AGAC);

console.log("\n--- kok ogedeki yedek deger zincirleri ---");
const KOK = harita(AGAC, {});
const kokAra = (n) => (n in KOK ? { deger: KOK[n], neden: null } : null);
for (const yazim of [
  "var(--zemin)",
  "var(--yok)",
  "var(--yok, var(--zemin))",
  "var(--yok, var(--de-yok, hsl(0 0% 100%)))",
  "calc(var(--aralik-1) * 2)",
]) {
  const c = coz(yazim, kokAra);
  console.log(`  ${yazim.padEnd(42)} -> ${c.deger === null ? "GECERSIZ: " + c.neden : c.deger}`);
}

console.log("\n--- dongu ---");
const D = { ad: ":root", bildirim: { "--a": "var(--b)", "--b": "var(--a)" }, cocuk: [] };
console.log("  --a: var(--b); --b: var(--a);  -> --a degeri:", harita(D, {})["--a"]);
--- agacta background-color: var(--kart-zemin, var(--zemin)) ---
  :root                                                         hsl(203 18% 97%)
  :root > .yan-bilgi                                            hsl(203 18% 97%)
  :root > .yan-bilgi > .olcum-kart                              hsl(203 18% 97%)
  :root > .yan-bilgi > .olcum-kart > .olcum-kart--uyari         hsl(0 66% 96%)
  :root > .yan-bilgi > .olcum-kart > .olcum-kart--sade          "yanlis-deger" -> hesaplanan degerde gecersiz
  :root > .yan-bilgi > .olcum-kart > .olcum-kart--yerel         hsl(203 18% 92%)

--- kok ogedeki yedek deger zincirleri ---
  var(--zemin)                               -> hsl(203 18% 97%)
  var(--yok)                                 -> GECERSIZ: --yok tanimsiz, yedek yok
  var(--yok, var(--zemin))                   -> hsl(203 18% 97%)
  var(--yok, var(--de-yok, hsl(0 0% 100%)))  -> hsl(0 0% 100%)
  calc(var(--aralik-1) * 2)                  -> calc(1.5rem * 2)

--- dongu ---
  --a: var(--b); --b: var(--a);  -> --a degeri: null

İkinci satır dersin en sık atlanan noktasıdır. .yan-bilgi ögesi --zemin değerini değiştiriyor, ama zemini hsl(203 18% 97%) kalıyor — çünkü okuduğu --kart-zemin, kök ögede zaten hesaplanmıştır.

Son satır çözümü gösteriyor: --kart-zemin alt ağaçta yeniden bildirildiğinde, var(--zemin) o ögede çözülür ve hsl(203 18% 92%) çıkar. Kural şudur: türev bir değerin bağlamdan etkilenmesi isteniyorsa, türev o bağlamda bildirilmelidir.

calc(var(--aralik-1) * 2) satırı bir başka ayrıntıyı gösteriyor: yerine koyma yalnız metin düzeyindedir. Değerin anlamlı olup olmadığı, yerine koyma bittikten sonra sınanır.

Yedek Değer ve Geçersizlik

var() ikinci bir argüman alır: adın tanımsız olduğu durumda kullanılacak yedek değer. Yedek değerler iç içe yazılabilir ve çıktının ikinci bloğu iki katlı bir zinciri çözüyor.

Yedeğin devreye girmediği bir durum vardır ve tuzak oradadır: ad tanımlıysa ama değeri kullanıldığı yerde anlamsızsa, yedek çalışmaz. --kart-zemin: yanlis-deger bildirimi tanımlıdır; background-color bu değeri alır ve o noktada geçersiz olur.

Bu duruma hesaplanan değerde geçersizlik (invalid at computed-value time) denir ve sonucu, ayrıştırma sırasındaki geçersizlikten farklıdır:

  • Ayrıştırma sırasında geçersiz bir bildirim atılır; aynı özelliğe yazan önceki bildirim yerinde kalır.
  • Hesaplanan değerde geçersiz bir bildirim atılmaz; özelliği unset durumuna getirir. Yani kalıtılan özelliklerde kalıtılan değer, kalıtılmayanlarda başlangıç değeri geçerli olur.

Önceki kural yerinde kalmadığı için sonuç genellikle beklenmedik olur. Bir renk bildirimi yanlış bir özel özellik yüzünden düştüğünde, ögenin rengi ana ögesinden gelir.

Döngü ayrı bir durumdur: birbirine başvuran iki özel özellik hiçbir değer üretmez ve ikisi de tanımsız sayılır.

Kayıtlı Özellikler

@property kuralı bir özel özelliği kaydeder: değerinin sözdizimini, kalıtılıp kalıtılmayacağını ve başlangıç değerini bildirir.

@property --kart-zemin {
  syntax: "<color>";
  inherits: true;
  initial-value: hsl(203 18% 97%);
}

Kaydın üç sonucu vardır. Birincisi tip denetimidir: bildirilen sözdizimine uymayan bir değer kaydın başlangıç değerine düşer, hesaplanan değerde geçersizlik oluşmaz. İkincisi kalıtımın denetlenebilmesidir; inherits: false yazılan bir özellik yalnız bildirildiği ögede geçerli olur. Üçüncüsü hareketle ilgilidir: kayıtsız bir özel özellik yalnız metindir ve ara değeri hesaplanamaz, kayıtlı bir renk ya da uzunluk ise geçişe ve anahtar karelere girebilir.

Üçüncü sonuç, önceki konudaki hareket bildirimleriyle birleşir: bir renk ölçeğinin kendisi animasyona sokulabilir hâle gelir.

Bileşenin Biçem Arayüzü

Buraya kadarki kurallar bir mimari kalıba varır. Bileşen, dışarıya açtığı ayar noktalarını özel özellik olarak tanımlar; içeride yalnız bu adları okur.

.olcum-kart {
  --kart-zemin: var(--zemin);
  --kart-cerceve: var(--cizgi);
  --kart-vurgu-yolu: -8px;

  background-color: var(--kart-zemin);
  border: 1px solid var(--kart-cerceve);
}

.olcum-kart:hover { transform: translateY(var(--kart-vurgu-yolu)); }

.olcum-kart--uyari { --kart-zemin: hsl(0 66% 96%); --kart-cerceve: var(--uyari); }
.olcum-kart--durgun { --kart-vurgu-yolu: 0px; }

Değiştiriciler artık bildirim ezmiyor, değer veriyor. .olcum-kart--uyari kuralı background-color yazmıyor; yalnız bileşenin okuduğu adı değiştiriyor. Bunun üç sonucu var: özgüllük yarışı çıkmaz, varyant sayısı arttıkça kural sayısı artmaz ve bileşenin neyin ayarlanabilir olduğu dosyanın ilk satırlarından okunur.

Ad öneki de bir sözleşmedir: --kart- ile başlayanlar bileşenin arayüzüdür, --zemin gibi öneksiz adlar sayfa ölçeğinden gelir. İkisi karışırsa bileşenin dışarıdan ne beklediği belirsizleşir.

Tema Yönetimi

Tema, aynı arayüzü farklı değerlerle doldurmaktır. İki katmanlı bir ölçek bunu ayırır: ham değerleri tutan adlar ve işlevi adlandıran adlar. Tema yalnız ikinci katmanı değiştirir.

:root {
  --gri-95: hsl(203 18% 97%);
  --gri-15: hsl(211 29% 16%);

  --zemin: var(--gri-95);
  --metin: var(--gri-15);
}

[data-tema="koyu"] {
  --zemin: var(--gri-15);
  --metin: var(--gri-95);
}

Tema bir öznitelikle bildirildiği için alt ağaç ölçeğinde de uygulanabilir: sayfanın tamamı açık, tek bir bölüm koyu olabilir. Kalıtım bunu kendiliğinden sağlar; ek bir kural gerekmez.

Aynı yapı ortam sorgusuyla da sürülebilir — kullanıcının renk şeması tercihi, öznitelik yerine sorgu içinde aynı iki bildirimi yazar. İki kaynak birlikte kullanılıyorsa hangisinin öncelikli olduğu bir karardır: kullanıcının sayfa içinde yaptığı seçim genellikle sistem tercihini ezer, çünkü daha sonra ve daha özgül yazılır.

Özet

  • Özel özellik bildirildiği ögede hesaplanır; alt ağaca hesaplanmış değer kalıtılır, ham metin değil.
  • Bir türev değerin bağlamdan etkilenmesi için o bağlamda yeniden bildirilmesi gerekir; kaynak adı alt ağaçta değiştirmek üstte hesaplanmış türevi değiştirmez.
  • Yedek değer yalnız ad tanımsızsa çalışır; tanımlı ama anlamsız bir değer hesaplanan değerde geçersizlik üretir ve özelliği unset durumuna getirir.
  • @property kaydı sözdizimi, kalıtım ve başlangıç değeri bildirir; kayıtlı özellikler tip denetimi kazanır ve geçişe girebilir.
  • Bileşen, ayar noktalarını önekli özel özellik olarak açtığında varyantlar bildirim ezmek yerine değer verir; özgüllük düz kalır ve arayüz okunur olur.

Sonraki Adım

Buradaki bütün adlar tarayıcıda, çalışma zamanında çözülüyor. Bir başka araç sınıfı aynı işlerin bir bölümünü daha belge tarayıcıya ulaşmadan yapar: iç içe yazımı düzleştirmek, yinelenen blokları tek yerde tutmak, dosyaları birleştirmek. Sonraki ders derleme zamanında çalışan biçem araçlarını ele alır ve hangi işin çalışma zamanına bırakılması gerektiğini ayırı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