İçeriğe geç
academia.sh

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-type bir ögeyi sorgu kabı yapar ve karşılığında sınırlama uygular; size değeri yüksekliğin dışarıdan verilmesini gerektirdiği için inline-size daha 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat