Ders 09 / 23
Kapsayıcı Sorguları
Sorgunun öznesinin görüntü alanından bileşenin kabına taşınması, kap bildirimi ve sınırlama gereği, kapsayıcı birimleri ve aynı bileşenin iki ayrı yerde doğru karar vermesi.
İçindekiler
Önceki dersteki bütün sorgular tek bir şeye bakıyordu: görüntü alanının kendisi. Sayfa düzeni için bu doğru öznedir; sayfanın kaç sütun olacağını gerçekten ekranın genişliği belirler.
Ölçüm kartı için doğru özne değildir. Kartın nasıl görünmesi gerektiğini belirleyen, ekranın genişliği değil kartın yerleştirildiği kabın genişliğidir. Bu ders sorgunun öznesini değiştirir ve değişimin ne kadar fark ettiğini hesapla ölçer.
Aynı Kart, İki Kap
İstasyon sayfasında ölçüm kartı iki yerde bulunur: ana bölümdeki kart ızgarasında ve yan bilgideki özet listesinde. İki kabın genişlikleri farklıdır ve bu genişlikler görüntü alanının genişliğinden doğrudan okunamaz.
Kartın kendi eşiği vardır: 280 birimin altında başlık, değer ve birim alt alta dizilir; üstünde yan yana durur. Aşağıdaki program bu kararı iki ayrı sorgu türüyle veriyor.
// kapsayici.mjs — ayni kartin iki ayri kapta aldigi olcu, iki sorgu turuyle karar const IC_BOSLUK = 48; const SAYFA_ARALIK = 24; const ANA_EN_DAR = 480, YAN_EN_DAR = 260; const KART = 200, KART_ARALIK = 16; // sayfa duzeni: minmax(480px, 2fr) minmax(260px, 1fr), tek sutun esigi 812px function bolumler(gorusAlani) { if (gorusAlani < 812) return { ana: gorusAlani - IC_BOSLUK, yan: gorusAlani - IC_BOSLUK }; let kalan = gorusAlani - IC_BOSLUK - SAYFA_ARALIK; let ana = (kalan * 2) / 3, yan = kalan / 3; if (yan < YAN_EN_DAR) { yan = YAN_EN_DAR; ana = kalan - yan; } if (ana < ANA_EN_DAR) { ana = ANA_EN_DAR; yan = kalan - ana; } return { ana, yan }; } // kart izgarasi: repeat(auto-fill, minmax(200px, 1fr)) function kartOlcusu(kapGenisligi) { const n = Math.max(1, Math.floor((kapGenisligi + KART_ARALIK) / (KART + KART_ARALIK))); return { sutun: n, olcu: (kapGenisligi - KART_ARALIK * (n - 1)) / n }; } // kartin kendi esigi: 280px altinda basliklar alt alta, ustunde yan yana const KART_ESIGI = 280; console.log("kartin yan yana duzene gectigi olcu: 280px"); console.log("ortam sorgusu karari: (min-width: 812px) -> her kart yan yana"); console.log("kapsayici sorgusu karari: (min-width: 280px) -> her kart kendi kabina gore\n"); console.log("gorus ana bolum yan bilgi ana kart yan kart ortam sorgusu kapsayici sorgusu"); console.log("alani ana / yan ana / yan"); const hatalar = []; for (const W of [400, 720, 812, 900, 1024, 1280, 1600]) { const { ana, yan } = bolumler(W); const anaKart = kartOlcusu(ana), yanKart = kartOlcusu(yan); const ortam = W >= 812 ? "yanyana" : "altalta"; const kapAna = anaKart.olcu >= KART_ESIGI ? "yanyana" : "altalta"; const kapYan = yanKart.olcu >= KART_ESIGI ? "yanyana" : "altalta"; if (ortam !== kapAna) hatalar.push(`${W}px ana bolum`); if (ortam !== kapYan) hatalar.push(`${W}px yan bilgi`); console.log( `${String(W).padStart(5)} ${ana.toFixed(0).padStart(9)} ${yan.toFixed(0).padStart(9)} ` + `${anaKart.olcu.toFixed(0).padStart(8)} ${yanKart.olcu.toFixed(0).padStart(8)} ` + `${(ortam + " / " + ortam).padEnd(18)} ${kapAna} / ${kapYan}`, ); } console.log(`\nortam sorgusunun yanlis karar verdigi durum sayisi: ${hatalar.length}`); for (const h of hatalar) console.log(` ${h}`);
kartin yan yana duzene gectigi olcu: 280px ortam sorgusu karari: (min-width: 812px) -> her kart yan yana kapsayici sorgusu karari: (min-width: 280px) -> her kart kendi kabina gore gorus ana bolum yan bilgi ana kart yan kart ortam sorgusu kapsayici sorgusu alani ana / yan ana / yan 400 352 352 352 352 altalta / altalta yanyana / yanyana 720 672 672 213 213 altalta / altalta altalta / altalta 812 480 260 232 260 yanyana / yanyana altalta / altalta 900 552 276 268 276 yanyana / yanyana altalta / altalta 1024 635 317 201 317 yanyana / yanyana altalta / yanyana 1280 805 403 258 403 yanyana / yanyana altalta / yanyana 1600 1019 509 243 247 yanyana / yanyana altalta / altalta ortam sorgusunun yanlis karar verdigi durum sayisi: 10 400px ana bolum 400px yan bilgi 812px ana bolum 812px yan bilgi 900px ana bolum 900px yan bilgi 1024px ana bolum 1280px ana bolum 1600px ana bolum 1600px yan bilgi
On dört karardan onu yanlış. Bu oran, ortam sorgusunun bileşen düzeyinde kötü bir araç olduğunu tek başına gösteriyor ama asıl ilginç olan hataların yönü.
İlk satır beklenenin tersini veriyor. Görüntü alanı 400 birim — dar bir ekran. Sayfa tek sütunlu olduğu için kart ızgarası tek sütun üretiyor ve kart 352 birim genişliğinde. Kart, dar ekranda geniş. Ortam sorgusu ise ekran dar olduğu için kartı alt alta diziyor.
Son satır aynı yanılgının öbür ucu. Görüntü alanı 1600 birim; ana bölüm 1019 birime çıkıyor ve kart ızgarası dört sütun üretiyor. Her kart 243 birim: kartın kendi eşiğinin altında. Geniş ekranda kart dar.
Aradaki satırlar üçüncü bir durumu gösteriyor: 1024 ve 1280 birimde ana bölümdeki kartlar dar, yan bilgideki kart geniş. Tek bir görüntü alanı ölçüsü, aynı anda iki farklı yanıt gerektiren iki kap içeriyor. Ortam sorgusunun verebileceği tek bir yanıt vardır ve ikisinden en az biri yanlış olur.
Sonuç bir kuraldır: sayfanın yapısı görüntü alanına, bileşenin biçimi kabına sorulur.
Kap Bildirmek
Kapsayıcı sorgusu (container query) iki adımdan oluşur. Önce bir öge sorgu kabı (query container) ilan edilir, sonra o kabın soyundan gelen ögeler kabın ölçüsünü sorgular.
container-type bildirimi kabı ilan eder ve üç değeri vardır. inline-size, kabın yalnız
satır içi eksendeki ölçüsünü sorgulanabilir kılar. size, iki ekseni birden açar.
normal ise ölçü sorgusu açmaz; yalnız biçem sorgularına izin verir.
Bildirimin bir bedeli vardır ve bu bedel bir mantık zorunluluğudur. Kabın ölçüsü sorgulanacaksa,
o ölçü sorgunun sonucundan etkilenmemelidir; aksi halde döngü oluşur: kap daralır, sorgu
değişir, içerik değişir, kap genişler, sorgu değişir. Bunu engellemek için kaba sınırlama
(containment) uygulanır: inline-size değeri satır içi eksende, size değeri iki eksende
kabın ölçüsünü içeriğinden bağımsız kılar.
Pratik sonucu şudur: container-type: size yazılan bir kaba yükseklik dışarıdan verilmelidir,
yoksa yüksekliği sıfır kabul edilir. inline-size bu sorunu doğurmaz, çünkü satır içi ölçü
zaten çoğu zaman kapsayanından gelir. Bu yüzden yaygın olan değer inline-size değeridir.
İkinci bildirim container-name, kaba ad verir. Adsız bir sorgu en yakın sorgu kabını
hedefler; adlı bir sorgu, o adı taşıyan en yakın kabı arar. İç içe kaplar bulunduğunda ad,
hangisinin sorgulandığını belirsizlikten çıkarır. container kısayolu ikisini birlikte yazar.
Bir sınır burada belirir ve unutulduğunda şaşırtır: bir öge kendisini sorgulayamaz. Sorgu kabına yazılan bildirimler kabın kendi ölçüsüne göre değişemez; yalnız soyundan gelenler değişebilir. Bileşenin en dış ögesi bu yüzden kap olarak ilan edilir ve biçimlendirilen ögeler onun içinde durur.
Kapsayıcı Birimleri
Sorgunun öznesi değiştiğinde uzunluk birimlerinin de karşılığı gerekir. Kapsayıcı birimleri
(container query units) kabın ölçüsünün yüzde birine karşılık gelir: cqi satır içi eksende,
cqb blok eksende, cqw ve cqh fiziksel eksenlerde, cqmin ve cqmax ikisinin küçüğü ve
büyüğü.
// cqbirim.mjs — kapsayici birimi (cqi) ile gorus alani biriminin (vw) karsilastirmasi const IC_BOSLUK = 48, SAYFA_ARALIK = 24; const ANA_EN_DAR = 480, YAN_EN_DAR = 260; const KART = 200, KART_ARALIK = 16, KOK = 16; function bolumler(gorusAlani) { if (gorusAlani < 812) return { ana: gorusAlani - IC_BOSLUK, yan: gorusAlani - IC_BOSLUK }; let kalan = gorusAlani - IC_BOSLUK - SAYFA_ARALIK; let ana = (kalan * 2) / 3, yan = kalan / 3; if (yan < YAN_EN_DAR) { yan = YAN_EN_DAR; ana = kalan - yan; } return { ana, yan }; } function kartOlcusu(kap) { const n = Math.max(1, Math.floor((kap + KART_ARALIK) / (KART + KART_ARALIK))); return (kap - KART_ARALIK * (n - 1)) / n; } const clamp = (k, t, b) => Math.max(k, Math.min(t, b)); const ALT = 0.875 * KOK; // 14px const UST = 1.25 * KOK; // 20px const SABIT = 0.75 * KOK; // 12px // cqi: kapsayicinin satir ici olcusunun yuzde biri const cqiDeger = (kap) => clamp(ALT, SABIT + (1.5 * kap) / 100, UST); // vw: gorus alaninin yuzde biri const vwDeger = (gorus) => clamp(ALT, SABIT + (1.5 * gorus) / 100, UST); console.log("font-size: clamp(0.875rem, 0.75rem + 1.5cqi, 1.25rem) (kok 16px)"); console.log("karsilastirma: ayni ifade 1.5vw ile yazilsaydi\n"); console.log("gorus ana kart yan kart cqi: ana cqi: yan vw: ana vw: yan vw ayrim yapiyor mu"); for (const W of [400, 720, 900, 1024, 1280, 1600]) { const { ana, yan } = bolumler(W); const anaKart = kartOlcusu(ana), yanKart = kartOlcusu(yan); const ca = cqiDeger(anaKart), cy = cqiDeger(yanKart); const va = vwDeger(W), vy = vwDeger(W); console.log( `${String(W).padStart(5)} ${anaKart.toFixed(0).padStart(8)} ${yanKart.toFixed(0).padStart(8)} ` + `${ca.toFixed(2).padStart(8)} ${cy.toFixed(2).padStart(8)} ` + `${va.toFixed(2).padStart(7)} ${vy.toFixed(2).padStart(7)} ${(va === vy ? "hayir" : "evet").padStart(19)}`, ); } console.log("\n--- ayni kart tek basina: kabin olcusu degisince cqi degeri ---"); console.log("kap olcusu 1cqi uygulanan yazi boyutu hangi bolge"); for (const kap of [180, 240, 300, 380, 460, 560]) { const bir = kap / 100; const d = cqiDeger(kap); const bolge = d === ALT ? "alt sinir" : d === UST ? "ust sinir" : "tercih edilen"; console.log( `${String(kap).padStart(10)} ${bir.toFixed(2).padStart(4)} ${d.toFixed(2).padStart(21)} ${bolge.padStart(11)}`, ); }
font-size: clamp(0.875rem, 0.75rem + 1.5cqi, 1.25rem) (kok 16px)
karsilastirma: ayni ifade 1.5vw ile yazilsaydi
gorus ana kart yan kart cqi: ana cqi: yan vw: ana vw: yan vw ayrim yapiyor mu
400 352 352 17.28 17.28 18.00 18.00 hayir
720 213 213 15.20 15.20 20.00 20.00 hayir
900 268 276 16.02 16.14 20.00 20.00 hayir
1024 201 317 15.01 16.76 20.00 20.00 hayir
1280 258 403 15.87 18.04 20.00 20.00 hayir
1600 243 247 15.64 15.70 20.00 20.00 hayir
--- ayni kart tek basina: kabin olcusu degisince cqi degeri ---
kap olcusu 1cqi uygulanan yazi boyutu hangi bolge
180 1.80 14.70 tercih edilen
240 2.40 15.60 tercih edilen
300 3.00 16.50 tercih edilen
380 3.80 17.70 tercih edilen
460 4.60 18.90 tercih edilen
560 5.60 20.00 ust sinir
Son sütun, görüntü alanı biriminin bileşen ölçeklemesinde neden yetersiz kaldığını gösteriyor:
vw iki kabı hiçbir satırda ayırt etmiyor. Aynı sayfada, aynı anda, iki farklı genişlikteki
kaba aynı değeri veriyor — üstelik 720 biriminden sonra üst sınıra dayandığı için değer hiç
değişmiyor.
Kapsayıcı birimi ise kabın ölçüsünü izliyor. 1024 birimlik görüntü alanında ana bölümdeki kart 15,01, yan bilgideki kart 16,76 birimlik yazı alıyor; ikisi de kendi genişliğine oranlı.
İkinci tablo tek bir kabın davranışını ayrıştırıyor. Kap genişledikçe 1cqi büyür ve tercih
edilen değer yükselir; 560 birimde üst sınıra dayanır ve kelepçelenir. CSS Fonksiyonları
dersindeki üç bölgeli davranış burada da geçerlidir, yalnız girdi değişmiştir.
Bir ayrıntı: kapsayıcı birimleri hiçbir sorgu kabının içinde olmayan bir ögede kullanılırsa
görüntü alanına göre çözülür. Birim yazmak kabı ilan etmez; container-type bildirimi ayrıca
yazılmalıdır.
Bileşen Kendi Eşiğini Taşır
Kapsayıcı sorgusunun asıl kazancı hesapta değil, biçem dosyasının yapısındadır.
Ortam sorgusuyla yazıldığında bileşenin eşiği, bileşenin bulunduğu her yer için ayrı ayrı hesaplanmak zorundadır: ana bölümdeki kart için bir eşik, yan bilgideki kart için başka bir eşik, sayfa düzeni değiştiğinde ikisi birden. Bileşen yeni bir yere taşındığında eşikler yeniden hesaplanır.
Kapsayıcı sorgusuyla yazıldığında eşik bileşenin kendisine aittir ve tek bir yerde durur. Bileşen nereye konursa konsun doğru kararı verir; sayfa düzeni değiştiğinde bileşenin biçem kodu değişmez.
/* duzen.css — 9. adım: kart kendi kabını sorgular */ .olcum-karti-kabi { container-type: inline-size; container-name: kart; } .olcum-karti { display: grid; gap: 0.25rem; font-size: clamp(0.875rem, 0.75rem + 1.5cqi, 1.25rem); } @container kart (min-width: 280px) { .olcum-karti { grid-template-columns: 1fr auto; align-items: baseline; } .olcum-karti .aciklama { grid-column: 1 / -1; } } @container kart (min-width: 420px) { .olcum-karti .egilim { display: block; } }
Kabın ayrı bir öge olması bir zorunluluktur: kart, kendisini sorgulayamaz. Kap yalnız
container-type ve container-name taşır; görsel bildirimlerin tamamı içerideki .olcum-karti
ögesindedir.
İki eşik iki ayrı soruya karşılık geliyor. 280 birimde başlık ve değer yan yana geçiyor; 420 birimde eğilim göstergesi görünür oluyor. İkisi de görüntü alanından değil kabın genişliğinden okunuyor ve iki farklı kapta iki farklı sonuç veriyor.
Sorgunun bir üçüncü biçimi daha vardır. Ölçü yerine bir özel özelliğin değeri sorgulanabilir;
@container style(--durum: uyari) yazımı, kapta tanımlı özelliğin değerine göre biçem ayırır.
Ölçü sorgusundan farkı, kabın ölçüsüne değil biçem durumuna bakmasıdır ve sınırlama gerektirmez.
Özet
- Sayfanın yapısı görüntü alanına, bileşenin biçimi bulunduğu kaba sorulur; iki soru aynı ölçüyle yanıtlanamaz.
- İstasyon sayfasının kart yerleşiminde ortam sorgusu on dört karardan onunda yanlış sonuç verir; dar ekranda kart geniş, geniş ekranda kart dar olabilir.
container-typebir ögeyi sorgu kabı yapar ve karşılığında sınırlama uygular;sizedeğeri yüksekliğin dışarıdan verilmesini gerektirdiği içininline-sizedaha az kısıtlıdır.- Bir öge kendisini sorgulayamaz; kap, biçimlendirilecek bileşenin dışında ayrı bir öge olarak ilan edilir.
- Kapsayıcı birimleri kabın ölçüsünün yüzde birine karşılık gelir ve aynı sayfadaki iki kabı ayırt eder; görüntü alanı birimi ayırt edemez.
- Eşik bileşenin kendisine yazıldığında bileşen taşındığında biçem kodu değişmez; ortam sorgusuyla yazılan eşik her yerleşim için yeniden hesaplanır.
Sonraki Adım
Bu derste yazı boyutu bir kez kabın genişliğine bağlandı ve clamp() ifadesinin içine
yerleştirildi. İfadenin üç sayısı — alt sınır, tercih edilen değer, üst sınır — seçilmiş gibi
göründü ama nasıl seçildikleri söylenmedi. Sonraki ders bu üç sayıyı hesaplanabilir kılar: iki
ölçü noktası verildiğinde aradaki doğrusal geçiş nasıl kurulur, ölçek adımları arasındaki oran
dar ekranda neden daralır ve yazı boyutunun kullanıcı ayarına saygı göstermesi ne demektir?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.