İçeriğe geç
academia.sh

Ders 08 / 23

Ortam Sorguları

Ortam sorgusunun bileşenleri, kuralların basamaklamadaki yeri, iki aralığın ucundaki çakışma ve boşluk, sorgu birimlerinin kullanıcı yazı boyutuyla ilişkisi ve özellik sorguları.

İçindekiler

Bir önceki ders türetilen eşiği verdi: 812 birim. Geriye bu sayının biçem dosyasında nasıl yazılacağı kalıyor.

Koşullu biçemin aracı ortam sorgusudur (media query). Sorgu bir soru sorar, yanıt doğruysa içindeki kurallar hesaba katılır. Bu ders sorunun neye bakabildiğini, kuralların basamaklamada nereye oturduğunu ve iki aralığın birleştiği noktada ne olduğunu ele alır.

Sorgunun Bileşenleri

Bir ortam sorgusu üç parçadan kurulur.

Ortam türü (media type), çıktının hangi ortama gittiğini söyler: screen ekran, print yazdırma ya da yazdırma önizlemesi, all her ikisi. Yazılmazsa all varsayılır.

Ortam özelliği (media feature), ortamın ölçülebilir bir niteliğini sınar ve parantez içinde yazılır. En sık kullanılanlar width ve height; bunların yanında ekranın yönünü veren orientation, en boy oranını veren aspect-ratio, piksel yoğunluğunu veren resolution bulunur. Bir başka grup, ekran ölçüsünü değil giriş yeteneğini sınar: pointer işaretleme aygıtının inceliğini, hover üzerine gelme davranışının bulunup bulunmadığını bildirir.

Mantıksal işleçler. and iki koşulu birlikte ister, virgül ise “ya da” anlamındadır; virgülle ayrılmış dallardan biri doğruysa sorgu doğrudur. not bir sorguyu tersine çevirir.

Özellik sınamasının iki yazımı vardır. Eskisi min- ve max- öneklerini kullanır ve kapalı aralık üretir: (min-width: 812px) bildirimi “genişlik 812’ye eşit ya da büyük” demektir. Yenisi karşılaştırma işleçlerini doğrudan yazar: (width >= 812px). İkinci yazım açık aralık da kurabilir ve iki sınırı tek koşulda birleştirebilir.

Değerlendirme ve Basamaklama

Sorgunun sonucu ile kuralın uygulanması iki ayrı adımdır. Sorgu doğruysa içindeki kurallar basamaklamaya katılır; hangi bildirimin kazandığına basamaklama karar verir.

Burada sık yapılan bir varsayım yanlıştır: ortam sorgusu özgüllük eklemez. Sorgunun içindeki .istasyon-duzeni seçicisi ile dışındaki .istasyon-duzeni seçicisi aynı özgüllüktedir; ikisi çakıştığında sonra yazılan kazanır.

// sorgu.mjs — ortam sorgusu degerlendiricisi ve basamaklama sonucu
// Desteklenen yazimlar: (min-width: Npx), (max-width: Npx), (width >= Npx),
// (Npx <= width < Mpx), (orientation: portrait|landscape), and, virgul, not.

function ozellikDegeri(ad, ortam) {
  if (ad === "width") return ortam.genislik;
  if (ad === "height") return ortam.yukseklik;
  if (ad === "orientation") return ortam.genislik >= ortam.yukseklik ? "landscape" : "portrait";
  throw new Error(`bilinmeyen ozellik: ${ad}`);
}

function ozellik(metin, ortam) {
  const s = metin.trim().replace(/^\(/, "").replace(/\)$/, "").trim();

  let m = s.match(/^(\d+(?:\.\d+)?)px\s*(<=|<)\s*([a-z-]+)\s*(<=|<)\s*(\d+(?:\.\d+)?)px$/);
  if (m) {
    const v = ozellikDegeri(m[3], ortam);
    const altOk = m[2] === "<=" ? Number(m[1]) <= v : Number(m[1]) < v;
    const ustOk = m[4] === "<=" ? v <= Number(m[5]) : v < Number(m[5]);
    return altOk && ustOk;
  }

  m = s.match(/^([a-z-]+)\s*(>=|<=|>|<)\s*(\d+(?:\.\d+)?)px$/);
  if (m) {
    const v = ozellikDegeri(m[1], ortam);
    const n = Number(m[3]);
    return m[2] === ">=" ? v >= n : m[2] === "<=" ? v <= n : m[2] === ">" ? v > n : v < n;
  }

  m = s.match(/^(min-|max-)?([a-z-]+)\s*:\s*(.+)$/);
  if (m) {
    const v = ozellikDegeri(m[2], ortam);
    const hedef = m[3].endsWith("px") ? Number(m[3].slice(0, -2)) : m[3];
    if (m[1] === "min-") return v >= hedef;
    if (m[1] === "max-") return v <= hedef;
    return v === hedef;
  }

  throw new Error(`cozulemeyen kosul: ${s}`);
}

function sorguEslesir(sorgu, ortam) {
  return sorgu.split(",").some((dal) => {
    const parcalar = dal.trim().split(/\s+and\s+/);
    const olumsuz = parcalar[0].startsWith("not ");
    parcalar[0] = parcalar[0].replace(/^not\s+/, "");
    const sonuc = parcalar.every((p) => ozellik(p, ortam));
    return olumsuz ? !sonuc : sonuc;
  });
}

const ortamlar = [
  { ad: "dar dikey", genislik: 380, yukseklik: 780 },
  { ad: "orta dikey", genislik: 720, yukseklik: 1024 },
  { ad: "esik ustu", genislik: 812, yukseklik: 1000 },
  { ad: "genis yatay", genislik: 1280, yukseklik: 800 },
  { ad: "alcak yatay", genislik: 900, yukseklik: 420 },
];

const kurallar = [
  { sorgu: null, deger: "tek sutun" },
  { sorgu: "(min-width: 812px)", deger: "iki sutun" },
  { sorgu: "(min-width: 1200px)", deger: "iki sutun + genis kenar" },
  { sorgu: "(orientation: landscape) and (max-height: 480px)", deger: "sikisik baslik" },
];

console.log("--- kurallar kaynak sirasinda, sonuncu eslesen kazanir ---");
kurallar.forEach((k, i) => console.log(`${i + 1}. ${k.sorgu ?? "(taban, sorgusuz)"} -> ${k.deger}`));

console.log("\nortam            genislik  yukseklik  eslesen kurallar  uygulanan");
for (const o of ortamlar) {
  const eslesen = kurallar
    .map((k, i) => (k.sorgu === null || sorguEslesir(k.sorgu, o) ? i + 1 : null))
    .filter((x) => x !== null);
  const kazanan = kurallar[eslesen[eslesen.length - 1] - 1].deger;
  console.log(
    `${o.ad.padEnd(15)}  ${String(o.genislik).padStart(8)}  ${String(o.yukseklik).padStart(9)}  ` +
      `${eslesen.join(",").padStart(16)}  ${kazanan}`,
  );
}

console.log("\n--- iki araligin ucundaki genislik ---");
const cift = [
  ["(max-width: 812px)", "(min-width: 812px)"],
  ["(max-width: 811.98px)", "(min-width: 812px)"],
  ["(width < 812px)", "(width >= 812px)"],
];
for (const [a, b] of cift) {
  console.log(`\n${a}  |  ${b}`);
  console.log("genislik  ilk  ikinci  durum");
  for (const g of [811, 811.99, 812, 813]) {
    const o = { genislik: g, yukseklik: 900 };
    const x = sorguEslesir(a, o);
    const y = sorguEslesir(b, o);
    const durum = x && y ? "IKISI DE eslesti" : x || y ? "tek kural" : "HICBIRI eslesmedi";
    console.log(
      `${String(g).padStart(8)}  ${String(x).padStart(5)}  ${String(y).padStart(6)}  ${durum}`,
    );
  }
}
--- kurallar kaynak sirasinda, sonuncu eslesen kazanir ---
1. (taban, sorgusuz) -> tek sutun
2. (min-width: 812px) -> iki sutun
3. (min-width: 1200px) -> iki sutun + genis kenar
4. (orientation: landscape) and (max-height: 480px) -> sikisik baslik

ortam            genislik  yukseklik  eslesen kurallar  uygulanan
dar dikey             380        780                 1  tek sutun
orta dikey            720       1024                 1  tek sutun
esik ustu             812       1000               1,2  iki sutun
genis yatay          1280        800             1,2,3  iki sutun + genis kenar
alcak yatay           900        420             1,2,4  sikisik baslik

--- iki araligin ucundaki genislik ---

(max-width: 812px)  |  (min-width: 812px)
genislik  ilk  ikinci  durum
     811   true   false  tek kural
  811.99   true   false  tek kural
     812   true    true  IKISI DE eslesti
     813  false    true  tek kural

(max-width: 811.98px)  |  (min-width: 812px)
genislik  ilk  ikinci  durum
     811   true   false  tek kural
  811.99  false   false  HICBIRI eslesmedi
     812  false    true  tek kural
     813  false    true  tek kural

(width < 812px)  |  (width >= 812px)
genislik  ilk  ikinci  durum
     811   true   false  tek kural
  811.99   true   false  tek kural
     812  false    true  tek kural
     813  false    true  tek kural

Birinci tablo eşleşmenin biriktiğini gösteriyor. 1280 birimlik bir ortamda üç kural birden doğrudur; uygulanan değer, doğru olanların sonuncusudur. Bu, kuralların yazım sırasının anlam taşıdığı anlamına gelir: aynı bildirimi hedefleyen sorgular dar aralıktan geniş aralığa doğru sıralanmazsa geniş aralığın kuralı dar aralığınki tarafından ezilir.

Dördüncü kural farklı bir eksende çalışıyor. 900 birim genişliğindeki alçak bir ortamda hem genişlik koşulu hem yön ve yükseklik koşulu doğru; sonuncusu kazanıyor. Genişlik tek ölçü değildir: yatay tutulan ve dikeyde yeri kısıtlı bir ortamda başlığın alçalması, genişlikle ilgisi olmayan bir karardır.

Aralıkların Ucu

İkinci bölüm, iki komşu aralığın birleştiği noktayı üç yazımla sınıyor.

Birinci çift klasik hatadır. max-width: 812px ve min-width: 812px yazımlarının ikisi de 812 birimde doğrudur, çünkü iki önek de kapalı sınır üretir. Tam o genişlikte iki kural birden uygulanır ve sonuç kaynak sırasına kalır. Hata görünmez olabilir — sonuç zaten istenen kural olabilir — ama bildirimler farklıysa ikisinin karışımı uygulanır.

İkinci çift, sık önerilen düzeltmeyi deniyor: üst sınırı biraz aşağı çekmek. Çakışma kalkıyor ama yerine bir boşluk geliyor. 811.99 birimlik bir genişlikte hiçbir kural eşleşmiyor. Kesirli genişlikler kuramsal bir olasılık değildir; yakınlaştırma ve ölçeklendirme kesirli CSS piksel değerleri üretir.

Üçüncü çift, karşılaştırma yazımıyla aralığı gerçekten böler. (width < 812px) ve (width >= 812px) birbirini dışlar ve birlikte bütün sayı doğrusunu kaplar. Ne çakışma ne boşluk kalır.

En sağlam yol, ikinci aralığı hiç yazmamaktır. Taban biçem dar düzeni tarif ediyorsa yalnız (min-width: 812px) yazılır; alt aralık için kural gerekmez ve uç noktası sorunu doğmaz.

Sorgudaki Birim

Uzunluk yazılan her yerde birim seçimi bir karardır. Ortam sorgusunda bu kararın alışılmadık bir sonucu vardır: sorgudaki em, kök ögeye yazılan yazı boyutuna göre değil, kullanıcının varsayılan yazı boyutuna göre çözülür.

// birim.mjs — ortam sorgusundaki uzunluk biriminin kullanici yazi boyutuyla iliskisi
// Sorgudaki em, kok ogeye yazilan font-size'a degil, kullanicinin varsayilan
// yazi boyutuna gore cozulur.

// 16px varsayilanda olculen icerik gereksinimleri
const ANA = 480, YAN = 260, ARALIK = 24, IC_BOSLUK = 48;
const TABAN = 16;
const GEREKEN_16 = ANA + YAN + ARALIK + IC_BOSLUK;

const emEsik = GEREKEN_16 / TABAN;   // ayni esigin em karsiligi
const pxEsik = GEREKEN_16;

console.log(`16px varsayilanda iki sutun icin gereken genislik: ${GEREKEN_16}px`);
console.log(`px yazilirsa: (min-width: ${pxEsik}px)`);
console.log(`em yazilirsa: (min-width: ${emEsik}em)\n`);

console.log("kullanici  gereken   px sorgusu  em sorgusu  px ile sapma  em ile sapma");
console.log("varsayilan genislik  tetiklenir  tetiklenir");
for (const d of [12, 16, 20, 24, 32]) {
  const gereken = (GEREKEN_16 * d) / TABAN;   // butun olculer rem tabanli oldugu icin olceklenir
  const pxTetik = pxEsik;
  const emTetik = emEsik * d;
  console.log(
    `${String(d + "px").padStart(10)}  ${gereken.toFixed(0).padStart(8)}  ` +
      `${pxTetik.toFixed(0).padStart(10)}  ${emTetik.toFixed(0).padStart(10)}  ` +
      `${(pxTetik - gereken).toFixed(0).padStart(12)}  ${(emTetik - gereken).toFixed(0).padStart(12)}`,
  );
}

console.log("\n--- 20px varsayilanda 812px genisliginde ne oluyor ---");
const d = 20;
const olcek = d / TABAN;
const alan = 812 - IC_BOSLUK * olcek - ARALIK * olcek;
const ana = (alan * 2) / 3, yan = alan / 3;
console.log(`ana bolum: ${ana.toFixed(1)}px  (gereken ${(ANA * olcek).toFixed(1)}px)`);
console.log(`yan bilgi: ${yan.toFixed(1)}px  (gereken ${(YAN * olcek).toFixed(1)}px)`);
console.log(`px sorgusu bu genislikte iki sutunu acar; em sorgusu ${(emEsik * d).toFixed(0)}px degerine kadar beklemis olur`);
16px varsayilanda iki sutun icin gereken genislik: 812px
px yazilirsa: (min-width: 812px)
em yazilirsa: (min-width: 50.75em)

kullanici  gereken   px sorgusu  em sorgusu  px ile sapma  em ile sapma
varsayilan genislik  tetiklenir  tetiklenir
      12px       609         812         609           203             0
      16px       812         812         812             0             0
      20px      1015         812        1015          -203             0
      24px      1218         812        1218          -406             0
      32px      1624         812        1624          -812             0

--- 20px varsayilanda 812px genisliginde ne oluyor ---
ana bolum: 481.3px  (gereken 600.0px)
yan bilgi: 240.7px  (gereken 325.0px)
px sorgusu bu genislikte iki sutunu acar; em sorgusu 1015px degerine kadar beklemis olur

Sağdaki iki sütun karşılaştırmayı tek başına yapıyor. Kullanıcı varsayılan yazı boyutunu değiştirdiğinde bütün ölçüler taban birime bağlı olduğu için birlikte büyür; iki sütunun gerektirdiği genişlik de büyür. Piksel yazılan sorgu bu değişimi göremez ve eşiği yerinde bırakır: 20 birimlik bir varsayılanda düzen, ihtiyacın 203 birim altında iki sütuna geçer. Taban birim yazılan sorgu ihtiyaçla birlikte kayar ve sapma her satırda sıfır kalır.

Son blok, sapmanın somut karşılığını veriyor: eşik erken tetiklendiğinde ana bölüm 481, yan bilgi 241 birime düşer; oysa büyütülmüş yazıyla aynı içeriğin 600 ve 325 birime ihtiyacı vardır. Yazı büyütmek, sayfayı okunur kılmak için yapılan bir ayardır; düzen bu ayarı ölçeğe katmazsa ayarın kendisi sonucu bozar.

Kural şudur: ortam sorgusunda uzunluk taban birimle yazılır. Aynı gerekçe yükseklik ve en boy oranı sınamaları için de geçerlidir.

Ortam Türleri ve Özellik Sorguları

Genişlik dışındaki iki sınama, ortam sorgularının yalnız ekran ölçüsüyle ilgili olmadığını gösterir.

@media print kuralları yazdırma çıktısına uygulanır. Yazdırılan bir ölçüm sayfasında gezinme çubuğunun, süzgeç alanının ve bağlantı süslemelerinin yeri yoktur; buna karşılık bağlantı adreslerinin metne yazılması ve sayfa kırılmalarının denetlenmesi gerekir. Çok sütunlu düzen dersinde tanıtılan break-inside bildirimi burada da çalışır.

Ortam sorgularından ayrı ama aynı biçimde yazılan ikinci bir yapı, özellik sorgusudur (feature query). @supports bir bildirimin tanınıp tanınmadığını sınar; ortamı değil, biçem dilinin çözülmesini sorgular. Izgara ve Esnek Kutu Seçimi dersinde bırakılan soru buradan yanıtlanır: subgrid değerinin tanındığı yerde iç ızgara kapsayanın hatlarını devralır, tanınmadığı yerde kendi şeritlerini tanımlar.

/* duzen.css — 8. adım: türetilen eşik ve yazdırma */
@media (min-width: 50.75em) {
  .istasyon-duzeni {
    grid-template-columns: minmax(480px, 2fr) minmax(260px, 1fr);
    grid-template-areas:
      "kunye     kunye"
      "olcumler  yan-bilgi"
      "konum     yan-bilgi";
  }
}

@supports (grid-template-columns: subgrid) {
  .olcum-karti {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 3;
  }
}

@media print {
  .gezinme,
  .olcum-suzgeci { display: none; }

  .istasyon-duzeni {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "kunye" "olcumler" "konum" "yan-bilgi";
  }

  .olcum-cizelgesi tr { break-inside: avoid; }
}

Üç blok, üç ayrı soru soruyor. Birincisi ortamın genişliğini sınıyor ve yalnız yapıyı değiştiriyor; ölçüler taban biçemdeki iç boyutlandırmadan geliyor. İkincisi bir bildirimin tanınıp tanınmadığını sınıyor ve tanınıyorsa kart satırlarını dış ızgaranın hatlarına bağlıyor. Üçüncüsü çıktının nereye gittiğini sınıyor.

Üçünün ortak yanı, hiçbirinin taban biçemde yazılanı geri almamasıdır. Her blok kendi koşulunda yeni bir bildirim ekler.

Özet

  • Ortam sorgusu ortam türü, parantez içinde yazılan ortam özellikleri ve and, virgül, not işleçlerinden kurulur; yazılmayan tür all sayılır.
  • Sorgu özgüllük eklemez; içindeki kurallar dışarıdakilerle aynı özgüllükte yarışır ve çakışmayı kaynak sırası çözer.
  • min- ve max- önekleri kapalı sınır üretir: aynı sayıya yazılan iki sorgu o noktada birlikte eşleşir; sınırı kesirle kaydırmak çakışmayı boşluğa çevirir.
  • Karşılaştırma yazımı aralığı çakışmasız ve boşluksuz böler; tek yönlü tek bir sorgu yazmak ise sorunu tamamen ortadan kaldırır.
  • Sorgudaki taban birim kullanıcının varsayılan yazı boyutuna göre çözülür ve eşiği içerikle birlikte kaydırır; piksel yazılan eşik bu ayarı göremez.
  • @media print çıktının ortamını, @supports biçem dilinin bir bildirimi tanıyıp tanımadığını sınar; ikisi de taban biçemi geri almadan bildirim ekler.

Sonraki Adım

Bu dersteki bütün sorgular tek bir şeye bakıyordu: görüntü alanının kendisi. Oysa ölçüm kartının nasıl görünmesi gerektiğini belirleyen, ekranın genişliği değil kartın içine yerleştirildiği yerin genişliğidir. Aynı kart geniş bir ekranda dar bir yan sütunda durabilir; görüntü alanı geniş olduğu için kart geniş düzenini açar ve sıkışır. Sonraki ders sorunun öznesini değiştirir: bir bileşen, bulunduğu kabın ölçüsünü nasıl sorgular?

İ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