Ders 07 / 26
Basamaklama ve Özgüllük
Aynı özelliğe yazan bildirimlerin kaynak, önem, özgüllük ve sıra ölçütleriyle sıralanması; özgüllük üçlüsünün hesabı ve onluk tabanda okunamayacağı.
İçindekiler
Önceki derslerin sonunda aynı ögeye birden çok kural uyar hâle geldi. Bir hücre td
seçicisiyle, .olcum-cizelgesi td seçicisiyle, td.eksik seçicisiyle ve #icerik td
seçicisiyle birden eşleşiyor. Dördü de text-align özelliğine yazarsa ögenin hizalaması ne
olur?
Bu sorunun yanıtı bir sezgi değil, tanımlı bir sıralamadır. Sıralamanın adı basamaklama (cascade)’dır ve dilin adındaki ilk harf ondan gelir. Bu ders basamaklamanın adımlarını sırayla uygular ve her adımı hesaplanabilir kılar.
Basamaklama Bildirimleri Sıralar
Önemli bir ayrımla başlamak gerekir: basamaklama kuralları değil, bildirimleri sıralar. Sıralama tek bir öge ve tek bir özellik için yapılır. Aynı kuralın iki bildirimi ayrı ayrı yarışır; birinin kazanması diğerinin de kazanacağı anlamına gelmez.
Bir öge–özellik çifti için geçerli bildirimler toplandıktan sonra dört ölçüt sırayla uygulanır. Önceki ölçütte bir kazanan çıkarsa sonraki ölçüte bakılmaz.
- Kaynak ve önem. Bildirimin nereden geldiği ve
!importanttaşıyıp taşımadığı. - Satır içi olması. Ögenin
styleözniteliğinden gelen bildirimler öne geçer. - Özgüllük. Seçicinin ne kadar dar bir küme hedeflediği.
- Sıra. Aynı özgüllükteki bildirimlerden sonra yazılan kazanır.
Kaynak üç tanedir: tarayıcının kendi varsayılan biçemi, kullanıcının tanımladığı biçem ve belgenin yazarının yazdığı biçem. Bu üçünün normal sıralaması zayıftan güçlüye şöyledir: tarayıcı, kullanıcı, yazar.
Özgüllük Bir Üçlüdür
Özgüllük tek bir sayı değil, üç sayıdan oluşan sıralı bir üçlüdür: .
- : seçicideki kimlik seçicilerinin sayısı.
- : sınıf seçicileri, öznitelik seçicileri ve sözde sınıfların toplam sayısı.
- : tür seçicileri ve sözde elemanların toplam sayısı.
Evrensel seçici * ve birleştiriciler hiçbir sütuna katkı vermez.
Aşağıdaki program bir seçici dizesini bu üçlüye çevirir.
// ozgulluk.mjs — bir secici dizesini (a, b, c) uclusune cevirir function ozgulluk(secici) { let s = secici; let a = 0, b = 0, c = 0; // :where(...) ozgullige katki vermez -> once cikar s = s.replace(/:where\([^()]*\)/g, ""); // :is( ) / :not( ) / :has( ) -> icindeki en yuksek ozgullugu devral const kapsayan = /:(?:is|not|has)\(([^()]*)\)/g; let m; while ((m = kapsayan.exec(s)) !== null) { const enYuksek = m[1] .split(",") .map((p) => ozgulluk(p.trim())) .reduce((en, o) => (karsilastir(o, en) > 0 ? o : en), [0, 0, 0]); a += enYuksek[0]; b += enYuksek[1]; c += enYuksek[2]; } s = s.replace(kapsayan, ""); // sozde elemanlar (::x) -> c; sozde siniflar (:x) -> b const sozdeEleman = s.match(/::[a-z-]+/g) ?? []; c += sozdeEleman.length; s = s.replace(/::[a-z-]+/g, ""); a += (s.match(/#[\w-]+/g) ?? []).length; s = s.replace(/#[\w-]+/g, ""); b += (s.match(/\.[\w-]+/g) ?? []).length; // sinif s = s.replace(/\.[\w-]+/g, ""); b += (s.match(/\[[^\]]*\]/g) ?? []).length; // oznitelik s = s.replace(/\[[^\]]*\]/g, ""); b += (s.match(/:[a-z-]+(\([^()]*\))?/g) ?? []).length; // sozde sinif s = s.replace(/:[a-z-]+(\([^()]*\))?/g, ""); c += (s.match(/[a-z][\w-]*/gi) ?? []).length; // tur secici (* sayilmaz) return [a, b, c]; } const karsilastir = (x, y) => x[0] - y[0] || x[1] - y[1] || x[2] - y[2]; const seciciler = [ "*", "td", ".olcum-cizelgesi td", "td.eksik", "tbody > tr > td", ".olcum-cizelgesi tbody td:not(.ad)", "input[type=\"number\"]", "#icerik td", ".olcum-cizelgesi td.deger.eksik", "td.eksik::after", ":where(.olcum-cizelgesi) td", ":is(#icerik, .yan) p", "tr:has(td.eksik)", ]; for (const s of seciciler) { const [a, b, c] = ozgulluk(s); console.log(`${a},${b},${c} ${s}`); }
0,0,0 * 0,0,1 td 0,1,1 .olcum-cizelgesi td 0,1,1 td.eksik 0,0,3 tbody > tr > td 0,2,2 .olcum-cizelgesi tbody td:not(.ad) 0,1,1 input[type="number"] 1,0,1 #icerik td 0,3,1 .olcum-cizelgesi td.deger.eksik 0,1,2 td.eksik::after 0,0,1 :where(.olcum-cizelgesi) td 1,0,1 :is(#icerik, .yan) p 0,1,2 tr:has(td.eksik)
Beş satır ayrıca okunmalıdır.
.olcum-cizelgesi td ile td.eksik aynı üçlüyü verdi: her ikisinde bir sınıf ve bir
tür var. Seçicilerin uzunluğu ya da ne kadar “kesin göründüğü” hesaba girmez; yalnızca
sayılar girer.
:not(), :is() ve :has() kendileri hesaba katılmaz; içlerindeki listenin en yüksek
özgüllüklü üyesini devralırlar. :is(#icerik, .yan) p bu yüzden verdi — listede
bir kimlik seçicisi bulunduğu için.
:where() ise sıfır katkı verir. :where(.olcum-cizelgesi) td üçlüsü, yalnızca td
yazılmış gibi ’dir. Bu, bir seçiciyi daraltırken özgüllüğü artırmama yoludur.
Sözde eleman üçüncü sütuna girer: td.eksik::after üçlüsünde ’dir.
Üçlü Onluk Tabanda Bir Sayı Değildir
Sık yapılan bir yanlış, üçlüyü yüzler–onlar–birler basamağı gibi okumaktır. Bu okuma on ve üzeri sayılarda bozulur, çünkü sütunlar arasında elde taşınmaz. Karşılaştırma sözlük sırasıdır: önce , eşitse , eşitse .
// onluk-degil.mjs — ozgulluk uclusu onluk tabanda bir sayi degildir const karsilastir = (x, y) => x[0] - y[0] || x[1] - y[1] || x[2] - y[2]; const ciftler = [ [[0, 11, 0], [1, 0, 0]], [[0, 0, 12], [0, 1, 0]], [[1, 0, 0], [0, 99, 99]], ]; for (const [x, y] of ciftler) { const s = karsilastir(x, y); const kazanan = s > 0 ? x : s < 0 ? y : "esit"; console.log(`${x.join(",")} vs ${y.join(",")} -> kazanan ${Array.isArray(kazanan) ? kazanan.join(",") : kazanan}`); console.log(` onluk okunsaydi: ${x[0]*100+x[1]*10+x[2]} vs ${y[0]*100+y[1]*10+y[2]}`); }
0,11,0 vs 1,0,0 -> kazanan 1,0,0 onluk okunsaydi: 110 vs 100 0,0,12 vs 0,1,0 -> kazanan 0,1,0 onluk okunsaydi: 12 vs 10 1,0,0 vs 0,99,99 -> kazanan 1,0,0 onluk okunsaydi: 100 vs 1089
İlk satır kritiktir: on bir sınıf taşıyan bir seçici, tek bir kimlik seçicisini yenemez. Onluk okuma tersini söylerdi. Sonuç şudur: bir kimlik seçicisiyle yazılmış bildirimi bastırmanın tek yolu, yine bir kimlik seçicisi yazmaktır. Önceki derste “biçem sınıfla yazılır” kuralının gerekçesi budur.
Basamaklamanın Uygulanışı
Aşağıdaki program yedi bildirimi tam sıralamaya sokar. Kaynak katmanları zayıftan güçlüye numaralanmıştır; sıralama ölçütleri sırayla uygulanır.
// basamaklama.mjs — ayni ozellige yazan bildirimleri siralar ve kazanani bulur // (ozgulluk hesabi onceki programdan; burada elle verilmistir) // katman sirasi: kucuk sayi = zayif const KATMAN = { "tarayici-normal": 0, "kullanici-normal": 1, "yazar-normal": 2, "yazar-satirici": 3, "yazar-onemli": 4, "kullanici-onemli": 5, "tarayici-onemli": 6, }; const bildirimler = [ { kaynak: "tarayici-normal", secici: "td", ozgulluk: [0,0,1], sira: 1, deger: "left" }, { kaynak: "yazar-normal", secici: "td", ozgulluk: [0,0,1], sira: 2, deger: "left" }, { kaynak: "yazar-normal", secici: ".olcum-cizelgesi td", ozgulluk: [0,1,1], sira: 3, deger: "right" }, { kaynak: "yazar-normal", secici: "td.ad", ozgulluk: [0,1,1], sira: 4, deger: "start" }, { kaynak: "yazar-normal", secici: "#icerik td", ozgulluk: [1,0,1], sira: 5, deger: "center" }, { kaynak: "yazar-onemli", secici: ".olcum-cizelgesi td.eksik",ozgulluk: [0,2,1], sira: 6, deger: "end" }, { kaynak: "kullanici-onemli",secici: "td", ozgulluk: [0,0,1], sira: 7, deger: "justify"}, ]; const karsilastir = (x, y) => KATMAN[x.kaynak] - KATMAN[y.kaynak] || x.ozgulluk[0] - y.ozgulluk[0] || x.ozgulluk[1] - y.ozgulluk[1] || x.ozgulluk[2] - y.ozgulluk[2] || x.sira - y.sira; function coz(aday) { const sirali = [...aday].sort(karsilastir); console.log("zayiftan gucluye:"); for (const b of sirali) { const o = b.ozgulluk.join(","); console.log(` ${b.kaynak.padEnd(17)} ${o} sira=${b.sira} ${b.secici.padEnd(28)} -> ${b.deger}`); } console.log(`kazanan: text-align: ${sirali.at(-1).deger}`); console.log(""); } console.log("=== tum bildirimler ==="); coz(bildirimler); console.log("=== yalnizca yazar-normal ==="); coz(bildirimler.filter((b) => b.kaynak === "yazar-normal")); console.log("=== ozgulluk esit, sira belirler ==="); coz(bildirimler.filter((b) => b.kaynak === "yazar-normal" && b.ozgulluk.join() === "0,1,1"));
=== tum bildirimler === zayiftan gucluye: tarayici-normal 0,0,1 sira=1 td -> left yazar-normal 0,0,1 sira=2 td -> left yazar-normal 0,1,1 sira=3 .olcum-cizelgesi td -> right yazar-normal 0,1,1 sira=4 td.ad -> start yazar-normal 1,0,1 sira=5 #icerik td -> center yazar-onemli 0,2,1 sira=6 .olcum-cizelgesi td.eksik -> end kullanici-onemli 0,0,1 sira=7 td -> justify kazanan: text-align: justify === yalnizca yazar-normal === zayiftan gucluye: yazar-normal 0,0,1 sira=2 td -> left yazar-normal 0,1,1 sira=3 .olcum-cizelgesi td -> right yazar-normal 0,1,1 sira=4 td.ad -> start yazar-normal 1,0,1 sira=5 #icerik td -> center kazanan: text-align: center === ozgulluk esit, sira belirler === zayiftan gucluye: yazar-normal 0,1,1 sira=3 .olcum-cizelgesi td -> right yazar-normal 0,1,1 sira=4 td.ad -> start kazanan: text-align: start
İkinci çalıştırma, ölçütlerin sırayla uygulandığını gösterir: tek bir kaynakta kalındığında kazanan özgüllüğe göre belirlendi. Üçüncü çalıştırmada özgüllük de eşitti ve karar yalnızca yazım sırasına kaldı; sonra yazılan kazandı.
Önem İşareti Sıralamayı Tersine Çevirir
Bir bildirimin sonuna !important yazıldığında bildirim ayrı bir katmana taşınır. Katman
listesindeki sıra dikkatle okunmalıdır: önemli bildirimlerde kaynak sıralaması ters
döner. Normal bildirimlerde yazar kullanıcıyı yener; önemli bildirimlerde kullanıcı yazarı
yener.
Bu ters çevirme bir kaza değil, bir tasarım kararıdır. Bir kullanıcının kendi
gereksinimlerini karşılamak üzere yazdığı biçem — yazı boyutunu büyütmek, kontrastı
artırmak, hareketi durdurmak — sayfa yazarının kararlarını geçebilmelidir. Çıktının ilk
bölümünde kullanici-onemli katmanının en üstte yer alması bunu gösteriyor.
Yazar tarafında !important yazmanın bedeli, kendi biçem dosyasında bir istisna katmanı
açmaktır. Bir kez yazıldığında onu bastırmanın tek yolu yine !important yazmaktır ve o
noktada özgüllük hesabı yeniden başlar. Uygulanabilir kural şudur: !important, bir çakışma
çözülemediği için değil, çözümü aranmadığı için yazılır; önce özgüllüğü düşürmek denenir.
Kaynak ve önemin arasına yerleşen bir mekanizma daha vardır: @layer ile tanımlanan
basamaklama katmanları. Yazar biçemi içinde adlandırılmış katmanlar açar ve katman
sırası özgüllükten önce değerlendirilir; böylece bir taban katmanının tamamı, özgüllüğü
ne olursa olsun, üstteki katman tarafından bastırılabilir.
Özgüllüğü Düşük Tutmak
Bu dersin pratik sonucu bir yazım disiplinidir.
- Seçiciler olabildiğince düz tutulur.
.olcum-cizelgesi tbody tr td.degeryerine.olcum-cizelgesi .degeryazmak, aynı ögeye daha düşük bir üçlüyle ulaşır. - Biçemde kimlik seçicisi kullanılmaz; ilk sütunu bir kez açan bir kural, sonraki bütün düzeltmeleri kimlik yazmaya zorlar.
- Bir seçiciyi bağlamla daraltmak gerekiyorsa
:where()yazılır; daraltma yapılır, özgüllük artmaz.
/* istasyon.css — 6. adım: özgüllüğü düz tutan yazım */ :where(.olcum-cizelgesi) .deger { text-align: right; } /* 0,1,0 */ :where(.olcum-cizelgesi) .ad { text-align: start; } /* 0,1,0 */ .eksik { color: #8a1c1c; } /* 0,1,0 */
Üç kural da aynı üçlüyü taşır. Bu, aralarındaki çakışmaların yalnızca yazım sırasıyla çözüleceği anlamına gelir — okunması ve öngörülmesi en kolay durum budur.
Özet
- Basamaklama kuralları değil bildirimleri sıralar; sıralama tek bir öge ve tek bir özellik için yapılır.
- Ölçütler sırayla uygulanır: kaynak ve önem, satır içi olma, özgüllük, yazım sırası. Önceki ölçüt bir kazanan çıkarırsa sonrakine bakılmaz.
- Özgüllük üçlüsüdür: kimlik sayısı, sınıf–öznitelik–sözde sınıf sayısı, tür–sözde eleman sayısı. Karşılaştırma sözlük sırasıdır; sütunlar arasında elde taşınmaz.
:is(),:not()ve:has()içindeki en yüksek özgüllüğü devralır;:where()sıfır katkı verir ve daraltmayı özgüllük artırmadan yapmayı sağlar.!importantbildirimi ayrı bir katmana taşır ve kaynak sıralamasını tersine çevirir; bu, kullanıcı biçeminin yazar kararlarını geçebilmesi için tanımlanmıştır.
Sonraki Adım
Basamaklama, bir ögeye uyan bildirimler arasından kazananı seçiyor. Peki hiçbir bildirim
uymuyorsa? Bir paragrafa hiçbir kural color yazmamışsa o paragrafın rengi ne olur? Sonraki
ders bu boşluğun iki ayrı mekanizmayla doldurulduğunu gösterir — kalıtım ve başlangıç değeri
— ve bu ikisini açıkça çağıran anahtar sözcükleri tanımlar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.