İçeriğe geç
academia.sh

Ders 07 / 23

Duyarlı Tasarım İlkesi

Duyarlılığın üç bileşeni, kesme noktalarının içerikten hesapla türetilmesi, iç boyutlandırmayla kendiliğinden çözülen eşikler ve şerit tanımının eşiği nasıl kaydırdığı.

İçindekiler

Çok sütunlu düzen dersinde bir karar sessizce verildi: sütun genişliği kapsayıcıya sığmadığında sütun sayısı düşürüldü. Eşik, bir ekran genişliği listesinden değil, metnin okunabilir kaldığı en dar ölçüden geldi.

Bu ders aynı akıl yürütmeyi sayfanın bütününe taşır. Bir düzenin hangi genişlikte değişmesi gerektiği hesaplanabilir bir sorudur; yanıtı aygıt adlarında değil, içeriğin kendi ölçülerindedir.

Duyarlılığın Üç Bileşeni

Duyarlı tasarım (responsive design) tek bir bildirimin adı değildir; üç davranışın birlikte bulunmasıdır.

Akışkan ızgara (fluid grid). Düzenin ölçüleri sabit uzunluklarla değil, oranlarla ve kalan alanla verilir. fr, yüzde, minmax() ve auto bu işi görür; sabit piksel genişlikler yalnız gerçekten sabit olması gereken yerlerde kalır.

Esnek medya. Görsel ve gömülü içerik kapsayıcısını aşmaz; ölçüsü kendi doğal boyutu değil, kapsayıcının bıraktığı yerdir.

Koşullu biçem. Yukarıdaki ikisi tek başına yetmediğinde — düzenin yapısı değişmek zorunda olduğunda — biçem bir koşula bağlanır.

Sıralama önemlidir. İlk iki bileşen ne kadar iş görürse, üçüncüsüne o kadar az iş kalır. Sorguyla yazılan her koşul, biçem dosyasında bakımı gereken bir daldır; iç boyutlandırmayla çözülen her eşik ise dosyada hiç görünmez.

Kesme Noktası Bir Ölçüdür

Kesme noktası (breakpoint), düzenin yapısının değiştiği genişliktir. Sorunun doğru sorulması şöyledir: bu bileşen, anlamını yitirmeden ne kadar daralabilir? Yanıt bir sayıdır ve bileşenin içeriğinden gelir.

Aşağıdaki program, ölçüm istasyonu sayfasının üç bileşeni için bu sayıyı hesaplıyor.

// kesme.mjs — kesme noktalarinin icerikten turetilmesi
// Sayfanin yatay ic bosluklari her iki yanda 24px.
const IC_BOSLUK = 24 * 2;

// 1) Metin sutunu: govde yazisi 18px, ortalama karakter genisligi 0.5em -> 9px/karakter
const KARAKTER = 9;
const OLCU_ALT = 45;   // okunabilir en az karakter
const OLCU_UST = 75;   // okunabilir en cok karakter
const metinAlt = OLCU_ALT * KARAKTER;
const metinUst = OLCU_UST * KARAKTER;

// 2) Olcum kartlari: bir kartin anlamli kaldigi en dar olcu ve aralik
const KART = 200;
const KART_ARALIK = 16;
const kartEsik = (n) => n * (KART + KART_ARALIK) - KART_ARALIK;

// 3) Sayfa duzeni: ana bolum ve yan bilgi yan yana durabilmek icin kendi en dar olculeri
const ANA_EN_DAR = 480;
const YAN_EN_DAR = 260;
const SAYFA_ARALIK = 24;
const ikiSutunEsik = ANA_EN_DAR + YAN_EN_DAR + SAYFA_ARALIK;

console.log("--- 1) metin sutunu ---");
console.log(`govde yazisi 18px, 1 karakter ~ ${KARAKTER}px`);
console.log(`olcu ${OLCU_ALT}-${OLCU_UST} karakter -> sutun ${metinAlt}-${metinUst}px`);
console.log(`gorus alani karsiligi: ${metinAlt + IC_BOSLUK}-${metinUst + IC_BOSLUK}px`);
console.log(`ust sinir asildiginda satir uzar: esik ${metinUst + IC_BOSLUK}px`);

console.log("\n--- 2) olcum kartlari ---");
console.log(`kart en dar ${KART}px, aralik ${KART_ARALIK}px`);
console.log("sutun  gereken icerik alani  gorus alani esigi");
for (let n = 1; n <= 5; n++) {
  console.log(
    `${String(n).padStart(5)}  ${String(kartEsik(n)).padStart(20)}  ${String(kartEsik(n) + IC_BOSLUK).padStart(18)}`,
  );
}

console.log("\n--- 3) sayfa duzeni ---");
console.log(`ana bolum en dar ${ANA_EN_DAR}px + yan bilgi en dar ${YAN_EN_DAR}px + aralik ${SAYFA_ARALIK}px`);
console.log(`iki sutunun mumkun oldugu en dar icerik alani: ${ikiSutunEsik}px`);
console.log(`gorus alani esigi (alt sinir): ${ikiSutunEsik + IC_BOSLUK}px`);

console.log("\n--- turetilen esiklerin tamami ---");
const esikler = [
  { ad: "kart 2 sutun", px: kartEsik(2) + IC_BOSLUK, sorgu: false },
  { ad: "kart 3 sutun", px: kartEsik(3) + IC_BOSLUK, sorgu: false },
  { ad: "metin ust siniri", px: metinUst + IC_BOSLUK, sorgu: false },
  { ad: "sayfa iki sutun*", px: ikiSutunEsik + IC_BOSLUK, sorgu: true },
  { ad: "kart 4 sutun", px: kartEsik(4) + IC_BOSLUK, sorgu: false },
  { ad: "kart 5 sutun", px: kartEsik(5) + IC_BOSLUK, sorgu: false },
];
esikler.sort((a, b) => a.px - b.px);
console.log("esik(px)  bilesen              sorgu gerekiyor mu");
for (const e of esikler) {
  console.log(
    `${String(e.px).padStart(8)}  ${e.ad.padEnd(20)} ${e.sorgu ? "evet" : "hayir (ic boyutlandirma yeter)"}`,
  );
}
const yazilacak = esikler.filter((e) => e.sorgu);
console.log(`\ntoplam esik: ${esikler.length}, yazilacak sorgu: ${yazilacak.length} (${yazilacak.map((e) => e.px + "px").join(", ")})`);
console.log("* en dar olculerin toplamindan gelen alt sinir; serit tanimina gore yukari cikar");
--- 1) metin sutunu ---
govde yazisi 18px, 1 karakter ~ 9px
olcu 45-75 karakter -> sutun 405-675px
gorus alani karsiligi: 453-723px
ust sinir asildiginda satir uzar: esik 723px

--- 2) olcum kartlari ---
kart en dar 200px, aralik 16px
sutun  gereken icerik alani  gorus alani esigi
    1                   200                 248
    2                   416                 464
    3                   632                 680
    4                   848                 896
    5                  1064                1112

--- 3) sayfa duzeni ---
ana bolum en dar 480px + yan bilgi en dar 260px + aralik 24px
iki sutunun mumkun oldugu en dar icerik alani: 764px
gorus alani esigi (alt sinir): 812px

--- turetilen esiklerin tamami ---
esik(px)  bilesen              sorgu gerekiyor mu
     464  kart 2 sutun         hayir (ic boyutlandirma yeter)
     680  kart 3 sutun         hayir (ic boyutlandirma yeter)
     723  metin ust siniri     hayir (ic boyutlandirma yeter)
     812  sayfa iki sutun*     evet
     896  kart 4 sutun         hayir (ic boyutlandirma yeter)
    1112  kart 5 sutun         hayir (ic boyutlandirma yeter)

toplam esik: 6, yazilacak sorgu: 1 (812px)
* en dar olculerin toplamindan gelen alt sinir; serit tanimina gore yukari cikar

Üç bileşen, üç ayrı ölçü kaynağı.

Metin sütununun ölçüsü tipografiden gelir. Bir satırda 45 karakterin altına inildiğinde göz çok sık satır atlar, 75 karakterin üstüne çıkıldığında satır sonundan sonrakinin başına dönmek zorlaşır. On sekiz taban ölçüsündeki bir gövde yazısında karakter genişliği yaklaşık dokuz birimdir; iki sınır 405 ve 675 birime karşılık gelir.

Kartların ölçüsü içeriğin kendisinden gelir: bir ölçüm kartında başlık, değer ve birim yan yana okunabilmelidir ve bu 200 birimin altında bozulur. Kaç sütun sığdığı buradan çıkar.

Sayfa düzeninin ölçüsü iki bölümün ölçülerinin toplamıdır. Yan yana durabilmeleri için gereken en dar alan, ikisinin en dar ölçüsü ile aralarındaki boşluğun toplamıdır.

Elde edilen liste altı eşik içeriyor ve hiçbiri bir aygıt adıyla anılmıyor. Sayılar tuhaf: 464, 680, 723, 812. Bu tuhaflık bir kusur değil, ölçünün gerçekten içerikten geldiğinin işaretidir.

Yazılmayan Eşikler

Altı eşikten yalnız biri koşullu biçem gerektiriyor. Kalan beşi, önceki derslerde kurulan iç boyutlandırma (intrinsic sizing) araçlarıyla kendiliğinden karşılanır.

Kart ızgarası repeat(auto-fill, minmax(200px, 1fr)) yazımıyla tanımlıdır. Sütun sayısı kapsayıcı genişliğinden hesaplanır ve 464, 680, 896, 1112 eşikleri hesabın doğal sonucu olarak geçilir. Bu eşikleri sorguyla yazmak aynı hesabı ikinci kez, elle yapmak olurdu.

Metin sütununun üst sınırı max-inline-size ile verilir; ölçü 675 birimi aşmaz, kalan alan dış boşluğa gider. Bunun için de koşul gerekmez.

Geriye sayfa düzeninin kendisi kalır. grid-template-areas bildirimi bir yapıdır, bir ölçü değil; iki sütunlu yerleşimden tek sütunluya geçmek bildirimin başka bir bildirimle değiştirilmesini gerektirir. Iç boyutlandırma bir ölçüyü hesaplayabilir, bir yapıyı seçemez.

Ölçüt buradan çıkar: ölçü değişiyorsa iç boyutlandırma, yapı değişiyorsa koşullu biçem.

Şerit Tanımı Eşiği Kaydırır

Yukarıdaki 812 sayısı en dar ölçülerin toplamıdır ve bir alt sınırdır. Gerçek eşik, şeritlerin nasıl tanımlandığına bağlıdır.

// liste.mjs — esigin nereden geldigi: serit tanimina gore turetme
const IC_BOSLUK = 48;      // sayfanin iki yanindaki ic bosluk toplami
const ARALIK = 24;         // iki sutun arasindaki aralik
const ANA_EN_DAR = 480;    // ana bolumun anlamli kaldigi en dar olcu
const YAN_EN_DAR = 260;    // yan bilginin anlamli kaldigi en dar olcu

// minmax(alt, Nfr) seritlerinin olculmesi: pay alt sinirin altinda kalirsa
// serit alt sinirda dondurulur, kalan alan yeniden bolusturulur.
function seritler(gorusAlani, tanim) {
  let kalan = gorusAlani - IC_BOSLUK - ARALIK;
  const sonuc = tanim.map((t) => ({ ...t, olcu: null }));
  let degisti = true;
  while (degisti) {
    degisti = false;
    const acik = sonuc.filter((t) => t.olcu === null);
    const frToplam = acik.reduce((s, t) => s + t.fr, 0);
    for (const t of acik) {
      const pay = (kalan * t.fr) / frToplam;
      if (pay < t.alt) {
        t.olcu = t.alt;
        kalan -= t.alt;
        degisti = true;
        break;
      }
    }
    if (!degisti) for (const t of acik) t.olcu = (kalan * t.fr) / frToplam;
  }
  return sonuc;
}

const tanimlar = [
  ["minmax(0, 2fr) minmax(0, 1fr)", [{ ad: "ana", fr: 2, alt: 0 }, { ad: "yan", fr: 1, alt: 0 }]],
  ["minmax(480px, 2fr) minmax(260px, 1fr)",
    [{ ad: "ana", fr: 2, alt: ANA_EN_DAR }, { ad: "yan", fr: 1, alt: YAN_EN_DAR }]],
];

for (const [ad, tanim] of tanimlar) {
  console.log(`--- ${ad} ---`);
  console.log("gorus alani  ana sutun  yan sutun  ana yeterli  yan yeterli  toplam tasma");
  for (const W of [768, 812, 852, 900, 1024]) {
    const s = seritler(W, tanim);
    const ana = s[0].olcu, yan = s[1].olcu;
    const tasma = ana + yan + ARALIK - (W - IC_BOSLUK);
    console.log(
      `${String(W).padStart(11)}  ${ana.toFixed(1).padStart(9)}  ${yan.toFixed(1).padStart(9)}  ` +
        `${(ana >= ANA_EN_DAR ? "evet" : "HAYIR").padStart(11)}  ` +
        `${(yan >= YAN_EN_DAR ? "evet" : "HAYIR").padStart(11)}  ${tasma.toFixed(1).padStart(12)}`,
    );
  }
  // her iki serit de en dar olcusunu tasmasiz karsilayan ilk genislik
  let esik = 300;
  while (esik < 2000) {
    const s = seritler(esik, tanim);
    const tasma = s[0].olcu + s[1].olcu + ARALIK - (esik - IC_BOSLUK);
    if (s[0].olcu >= ANA_EN_DAR && s[1].olcu >= YAN_EN_DAR && tasma <= 0.001) break;
    esik += 1;
  }
  console.log(`turetilen kesme noktasi: ${esik}px\n`);
}

console.log("--- aygit adlarindan gelen bir esik ile karsilastirma ---");
const HAZIR = 768;
for (const [ad, tanim] of tanimlar) {
  const s = seritler(HAZIR, tanim);
  const tasma = s[0].olcu + s[1].olcu + ARALIK - (HAZIR - IC_BOSLUK);
  const dar = s[0].olcu < ANA_EN_DAR || s[1].olcu < YAN_EN_DAR;
  const tani = dar ? "seritler en dar olcunun altinda" : `seritler yeterli ama ${tasma.toFixed(0)}px tasma var`;
  console.log(`${HAZIR}px, ${ad}`);
  console.log(`  ana ${s[0].olcu.toFixed(1)}  yan ${s[1].olcu.toFixed(1)}  -> ${tani}`);
}
--- minmax(0, 2fr) minmax(0, 1fr) ---
gorus alani  ana sutun  yan sutun  ana yeterli  yan yeterli  toplam tasma
        768      464.0      232.0        HAYIR        HAYIR           0.0
        812      493.3      246.7         evet        HAYIR           0.0
        852      520.0      260.0         evet         evet           0.0
        900      552.0      276.0         evet         evet           0.0
       1024      634.7      317.3         evet         evet           0.0
turetilen kesme noktasi: 852px

--- minmax(480px, 2fr) minmax(260px, 1fr) ---
gorus alani  ana sutun  yan sutun  ana yeterli  yan yeterli  toplam tasma
        768      480.0      260.0         evet         evet          44.0
        812      480.0      260.0         evet         evet           0.0
        852      520.0      260.0         evet         evet           0.0
        900      552.0      276.0         evet         evet           0.0
       1024      634.7      317.3         evet         evet           0.0
turetilen kesme noktasi: 812px

--- aygit adlarindan gelen bir esik ile karsilastirma ---
768px, minmax(0, 2fr) minmax(0, 1fr)
  ana 464.0  yan 232.0  -> seritler en dar olcunun altinda
768px, minmax(480px, 2fr) minmax(260px, 1fr)
  ana 480.0  yan 260.0  -> seritler yeterli ama 44px tasma var

İki tanım, iki farklı eşik.

Birinci tanımda oran kilitlidir: yan bilgi her genişlikte kalanın üçte birini alır. Yan bilginin 260 birime ulaşması için üçte birin 260 olması gerekir; eşik 852 birime çıkar. Alt sınırların toplamı olan 812 burada yetmez, çünkü ana bölüm ihtiyacından fazlasını almaktadır.

İkinci tanımda alt sınırlar şeride yazılıdır. Şeritler önce en dar ölçülerine oturur, artan alan orana göre bölüşülür. Eşik alt sınırların toplamına, yani 812 birime iner. Bu tanım sayfaya 40 birim genişlik kazandırır ve kesme noktasını hesabın ilk sonucuyla aynı yere getirir.

Son blok, eşiğin dışarıdan seçilmesinin iki ayrı biçimde nasıl kırıldığını gösteriyor. Aygıt adlarından gelen 768 birimlik eşikte birinci tanımda şeritler en dar ölçünün altında kalır: düzen uygulanır ama içerik sıkışır. İkinci tanımda şeritler alt sınırlarını korur, bu kez toplamları kapsayıcıyı 44 birim aşar: yatay taşma oluşur.

İki sonuç da aynı nedenden doğar. Eşik içerikten türetilmediğinde, ölçüler ile eşik arasındaki bağ kopar ve hata ya sıkışma ya taşma olarak görünür.

Dar Taraftan Başlamak

Kesme noktası bulunduktan sonra bir yazım kararı kalır: taban biçem hangi düzeni tarif etsin?

Taban biçem dar düzeni tarif ediyorsa, koşul genişledikçe eklenir. Tek sütunlu yerleşim zaten yazılıdır; koşul yalnız ikinci sütunu kurar.

Taban biçem geniş düzeni tarif ediyorsa, koşul daraldıkça eklenir ve her koşulun içinde tabanın kurduğu şeyi geri alan bildirimler bulunur: alan adlarını sıfırlamak, şerit sayısını bire indirmek, dış boşlukları küçültmek.

Dar taraftan başlamak, geri alma bildirimlerini ortadan kaldırır. İkinci bir gerekçe daha vardır: içerik dar bir alanda önceliklendirilmek zorundadır. Hangi bölümün önce geleceği, hangi bilginin taşınabilir olduğu dar düzende karara bağlanır; geniş düzen bu kararların üzerine kurulur. Ters yönde çalışıldığında dar düzen, geniş düzenden arta kalan olur.

/* duzen.css — 7. adım: taban dar düzen, tek eşik */
.istasyon-duzeni {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "kunye"
    "olcumler"
    "yan-bilgi"
    "konum";
  gap: var(--aralik-1);
}

.olcum-kartlari {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--aralik-0);
}

.istasyon-notlari { max-inline-size: 675px; }

Taban biçemde tek bir sorgu yoktur. Kart ızgarası genişlikle birlikte sütun kazanır, metin sütunu üst sınırında durur, sayfa tek sütunludur. Yalnız 812 birimlik eşikte yapı değişir; o bildirim sonraki derste yazılacaktır.

Görüntü Alanı Bildirimi

Bir ön koşul, bütün bu hesabın geçerli olması için gereklidir. Küçük ekranlı aygıtlarda tarayıcı, dar ekrana göre yazılmamış sayfaları okunur kılmak için geniş bir sanal görüntü alanı varsayıp sonucu küçülterek gösterebilir. Bu durumda CSS’in gördüğü genişlik ekranın gerçek genişliği olmaz ve türetilen eşikler hiç tetiklenmez.

Web’in Temelleri ve HTML kursunda tanıtılan görüntü alanı meta etiketi bu varsayımı kapatır:

<meta name="viewport" content="width=device-width, initial-scale=1">

Bildirim, CSS’in gördüğü genişliği aygıtın kendi genişliğine eşitler. Duyarlı biçemin ilk satırı biçem dosyasında değil, belgenin baş bölümündedir.

Özet

  • Duyarlılık üç bileşenin birlikte bulunmasıdır: akışkan ölçüler, kapsayıcısını aşmayan medya ve gerektiğinde koşullu biçem.
  • Kesme noktası bir aygıt genişliği değil, bileşenin anlamını yitirmeden inebildiği en dar ölçüden türetilen bir sayıdır; türetilen sayılar yuvarlak olmaz.
  • Ölçü değişiyorsa iç boyutlandırma yeter; yapı değişiyorsa koşullu biçem gerekir. Istasyon sayfasında altı eşikten yalnız biri sorguya dönüşür.
  • Şerit tanımı eşiği kaydırır: oran kilitli tanımda eşik 852, alt sınırları yazılı tanımda 812 birimdir.
  • Eşik dışarıdan seçildiğinde hata iki biçimde görünür — şeritler en dar ölçünün altına iner ya da toplamları kapsayıcıyı aşarak taşma üretir.
  • Taban biçem dar düzeni tarif ettiğinde koşullar ekleme yapar; geniş düzenden başlandığında her koşul geri alma bildirimleri taşır.

Sonraki Adım

Türetilen eşik elde var: 812 birim. Geriye bu sayının biçem dosyasında nasıl yazılacağı kalıyor. Koşul yalnız genişlikle kurulmaz; yüksekliğe, ekranın yönüne, çıktının kâğıda mı ekrana mı gideceğine göre de biçem ayrılabilir. Sonraki ders koşullu biçemin yazımını ele alır: bir koşul hangi özelliklere bakabilir, iki koşul nasıl birleştirilir ve iki aralığın ucundaki genişlikte hangi kural kazanı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