İçeriğe geç
academia.sh

Ders 06 / 23

Çok Sütunlu Düzen

Metin akışının sütunlara bölünmesi, sütun sayısı ile sütun genişliği bildirimlerinin birlikte çözülmesi, dengeleme kuralı, kırılma denetimi ve çok sütunlu düzenin uygun olmadığı durumlar.

İçindekiler

Buraya kadarki bütün düzenlerde birim bir kutuydu: kart, bölüm, düğme. Kutular hatlara yerleştirildi ya da eksende dağıtıldı. Sayfadaki uzun açıklama metni ise tek bir kutudur ve geniş bir kapsayıcıda tek bir satır dizisi olarak uzar.

Bu ders metnin kendisini bölen modeli ele alır. Çok sütunlu düzen (multi-column layout), bir kutunun içeriğini birden çok sütuna dağıtır; kutuyu bölmez, akışı böler. Ayrım küçük görünür ama düzenin bütün davranışını belirler.

Akışı Bölmek, Kutuyu Bölmek Değil

Bir kutuya columns bildirildiğinde kutunun kendisi tek bir kutu olarak kalır. Kenarlığı, arka planı ve dış boşluğu bütüne uygulanır. Değişen, kutunun içindeki satırların yerleştirilmesidir: satırlar birinci sütunu doldurur, dolduğunda ikinci sütuna geçer.

Bu, esnek kutu ve ızgaradan iki noktada ayrılır. Birincisi, sütunlar birer öge değildir; seçiciyle hedeflenemez, sayıları belgede görünmez. İkincisi, tek bir paragraf iki sütuna bölünebilir: metnin ilk üç satırı birinci sütunda, kalan iki satırı ikinci sütunda yer alabilir. Izgarada bir öge iki şeride bölünmez, yayılır; burada içerik gerçekten parçalanır.

Bu yüzden çok sütunlu düzen bir sayfa düzeni aracı değildir. Sayfa bölümlerini yan yana koymak için ızgara vardır; çok sütunlu düzen, tek bir sürekli metnin okunabilir genişlikte kalması için kullanılır.

Sayı mı, Genişlik mi

İki bildirim vardır ve ikisi de aynı hesaba girer. column-count kaç sütun olacağını, column-width bir sütunun en az ne kadar geniş olacağını söyler. columns kısayolu ikisini birlikte yazar.

Hangisi yazılırsa yazılsın, kullanılan sütun sayısı ve kullanılan sütun genişliği tek bir kuralla türetilir.

// sutun.mjs — cok sutunlu duzende kullanilan sutun sayisi ve genisligi
// CSS Multi-column sozde algoritmasi: sayi ve genislikten hangisi yazilmissa
// digeri kapsayicinin genisliginden turetilir.
const GAP = 32; // column-gap: 2rem (kok 16px)

function coz(W, { genislik, sayi }) {
  let kullanilanSayi;
  if (sayi === "auto") {
    kullanilanSayi = Math.max(1, Math.floor((W + GAP) / (genislik + GAP)));
  } else if (genislik === "auto") {
    kullanilanSayi = sayi;
  } else {
    kullanilanSayi = Math.min(sayi, Math.max(1, Math.floor((W + GAP) / (genislik + GAP))));
  }
  const kullanilanGenislik = (W - GAP * (kullanilanSayi - 1)) / kullanilanSayi;
  return { sayi: kullanilanSayi, genislik: kullanilanGenislik };
}

const durumlar = [
  ["column-width: 18rem", { genislik: 288, sayi: "auto" }],
  ["column-count: 3", { genislik: "auto", sayi: 3 }],
  ["columns: 18rem 3", { genislik: 288, sayi: 3 }],
];

console.log(`column-gap: 2rem = ${GAP}px   (18rem = 288px, kok 16px)`);
for (const [ad, bildirim] of durumlar) {
  console.log(`\n--- ${ad} ---`);
  console.log("kapsayici  sutun  sutun genisligi");
  for (const W of [320, 480, 640, 800, 1024, 1280]) {
    const { sayi, genislik } = coz(W, bildirim);
    console.log(
      `${String(W).padStart(9)}  ${String(sayi).padStart(5)}  ${genislik.toFixed(1).padStart(15)}`,
    );
  }
}

// column-width bir alt sinirdir: n sutun uretildiginde kullanilan genislik
// [U, ((n+1)U + G)/n) araliginda kalir. Aralik n buyudukce daralir.
console.log("\n--- column-width bir alt sinirdir: kullanilan genisligin araligi ---");
console.log("U = 288px (18rem), G = 32px");
console.log("sutun  en dar  en genis (sinira yaklasirken)  oran");
for (let n = 1; n <= 5; n++) {
  const U = 288;
  const enDar = U;
  const enGenis = ((n + 1) * U + GAP) / n;
  console.log(
    `${String(n).padStart(5)}  ${enDar.toFixed(1).padStart(6)}  ` +
      `${enGenis.toFixed(1).padStart(28)}  ${(enGenis / enDar).toFixed(2).padStart(4)}`,
  );
}
column-gap: 2rem = 32px   (18rem = 288px, kok 16px)

--- column-width: 18rem ---
kapsayici  sutun  sutun genisligi
      320      1            320.0
      480      1            480.0
      640      2            304.0
      800      2            384.0
     1024      3            320.0
     1280      4            296.0

--- column-count: 3 ---
kapsayici  sutun  sutun genisligi
      320      3             85.3
      480      3            138.7
      640      3            192.0
      800      3            245.3
     1024      3            320.0
     1280      3            405.3

--- columns: 18rem 3 ---
kapsayici  sutun  sutun genisligi
      320      1            320.0
      480      1            480.0
      640      2            304.0
      800      2            384.0
     1024      3            320.0
     1280      3            405.3

--- column-width bir alt sinirdir: kullanilan genisligin araligi ---
U = 288px (18rem), G = 32px
sutun  en dar  en genis (sinira yaklasirken)  oran
    1   288.0                         608.0  2.11
    2   288.0                         448.0  1.56
    3   288.0                         394.7  1.37
    4   288.0                         368.0  1.28
    5   288.0                         352.0  1.22

Üç bildirim biçimi, üç ayrı davranış.

column-width tek başına yazıldığında sütun sayısı kapsayıcıdan türetilir ve üst sınırı yoktur: kapsayıcı genişledikçe sütun eklenir. Çok geniş bir ekranda beş, altı sütun oluşması istenmiyorsa bu bildirim tek başına yetmez.

column-count tek başına yazıldığında sütun sayısı sabittir ve kapsayıcı daraldığında sütunlar birlikte daralır. Çıktının ikinci bloğu bunun sonucunu gösteriyor: 320 birimlik bir kapsayıcıda üç sütun, her biri 85 birim. Bir sütuna on karakter sığar; metin okunamaz hâle gelir. Bu bildirim, dar kapsayıcıya uyarlanmak için tek başına kullanılamaz.

columns: 18rem 3 ikisini birleştirir ve şu cümleyi yazar: “en fazla üç sütun, ama her sütun en az 18 taban birim genişliğinde olsun.” Sütun sayısı iki koşulun küçüğüdür; dar kapsayıcıda genişlik koşulu, geniş kapsayıcıda sayı koşulu bağlayıcı olur. Uygulamada aranan davranış çoğu zaman budur.

Son blok, sıkça yanlış anlaşılan bir noktayı sayıya döküyor. column-width bir hedef değil, bir alt sınırdır. Kapsayıcı 18 taban birimden geniş olduğu sürece kullanılan genişlik bu değerin altına inmez, ama üstüne çıkabilir: tek sütunlu durumda iki katından fazlasına kadar. Sütun sayısı arttıkça bu oran daralır, çünkü yeni bir sütun daha erken eklenir. Bu yüzden column-width değeri, kabul edilebilir en dar ölçüye göre seçilir; okumak için ideal olan ölçüye göre değil.

Kapsayıcı column-width değerinden dar olduğunda alt sınır uygulanamaz. Sütun sayısı bire düşer ve genişlik kapsayıcıya eşitlenir; taşma oluşmaz.

İçerik Sütunlara Nasıl Dağılır

Sütunların ölçüsü belli olduğunda ikinci soru gelir: hangi satır hangi sütuna gider? column-fill bunu bildirir. balance değeri sütun yüksekliklerini eşitlemeye çalışır, auto değeri sütunları sırayla doldurur ve bunun için kutuya bir yükseklik verilmiş olmasını gerektirir.

Dengeleme, bölünmez bloklar olmadığında yalnız bir bölmedir. Bölünmez bloklar girdiğinde sonuç değişir.

// denge.mjs — sutun dengeleme modeli: icerik sutunlara hangi yukseklikte boluniyor
// Olcu birimi satirdir. Bir blok "bolunmez" ise break-inside: avoid yazilmis demektir.
const SUTUN = 3;

const icerik = [
  { ad: "giris-p", satir: 6, bolunur: true },
  { ad: "yontem-p", satir: 5, bolunur: true },
  { ad: "olcu-listesi", satir: 7, bolunur: false },
  { ad: "ara-p", satir: 4, bolunur: true },
  { ad: "sekil", satir: 9, bolunur: false },
  { ad: "sonuc-p", satir: 8, bolunur: true },
  { ad: "not-p", satir: 3, bolunur: true },
];

// Verilen sutun yuksekligiyle icerigi sirayla sutunlara dagitir.
function paketle(H) {
  const sutunlar = [[]];
  let dolu = 0;
  const yeniSutun = () => { sutunlar.push([]); dolu = 0; };

  for (const blok of icerik) {
    if (!blok.bolunur) {
      if (dolu > 0 && dolu + blok.satir > H) yeniSutun();
      sutunlar[sutunlar.length - 1].push(`${blok.ad}(${blok.satir})`);
      dolu += blok.satir;
      continue;
    }
    let kalan = blok.satir;
    let parca = 0;
    while (kalan > 0) {
      if (dolu >= H) yeniSutun();
      const konan = Math.min(kalan, H - dolu);
      parca += 1;
      const etiket = kalan === blok.satir && konan === blok.satir
        ? `${blok.ad}(${konan})`
        : `${blok.ad}.${parca}(${konan})`;
      sutunlar[sutunlar.length - 1].push(etiket);
      dolu += konan;
      kalan -= konan;
    }
  }
  const yukseklikler = sutunlar.map((s) =>
    s.reduce((t, e) => t + Number(e.match(/\((\d+)\)/)[1]), 0));
  return { sutunlar, yukseklikler };
}

const toplam = icerik.reduce((t, b) => t + b.satir, 0);
console.log(`toplam ${toplam} satir, ${SUTUN} sutun, esit bolunme ${(toplam / SUTUN).toFixed(2)} satir`);
console.log("bolunmez bloklar: " + icerik.filter((b) => !b.bolunur).map((b) => `${b.ad}(${b.satir})`).join(", "));

// column-fill: balance — sutun sayisini asmayan en kucuk yukseklik aranir
let H = Math.ceil(toplam / SUTUN);
let sonuc = paketle(H);
while (sonuc.sutunlar.length > SUTUN) {
  H += 1;
  sonuc = paketle(H);
}
console.log(`\n--- column-fill: balance  (bulunan yukseklik: ${H} satir) ---`);
sonuc.sutunlar.forEach((s, i) => {
  console.log(`sutun ${i + 1} (${sonuc.yukseklikler[i]} satir): ${s.join(" | ")}`);
});
console.log(`en yuksek - en alcak fark: ${Math.max(...sonuc.yukseklikler) - Math.min(...sonuc.yukseklikler)} satir`);

// column-fill: auto — yukseklik disaridan verilir, sutunlar sirayla doldurulur
const SABIT = 12;
const oto = paketle(SABIT);
console.log(`\n--- column-fill: auto, block-size: ${SABIT} satir ---`);
oto.sutunlar.forEach((s, i) => {
  const tasma = i >= SUTUN ? "  <- tanimli sutun sayisini asti" : "";
  console.log(`sutun ${i + 1} (${oto.yukseklikler[i]} satir): ${s.join(" | ")}${tasma}`);
});

// bolunmez bloklar kaldirilirsa dengeleme ne kadar iyilesir
const eskiIcerik = icerik.map((b) => ({ ...b }));
for (const b of icerik) b.bolunur = true;
let H2 = Math.ceil(toplam / SUTUN);
let s2 = paketle(H2);
while (s2.sutunlar.length > SUTUN) { H2 += 1; s2 = paketle(H2); }
console.log(`\n--- ayni icerik, hicbir blokta break-inside: avoid yok ---`);
console.log(`bulunan yukseklik: ${H2} satir (bolunmez bloklarla: ${H} satir)`);
s2.sutunlar.forEach((s, i) => {
  console.log(`sutun ${i + 1} (${s2.yukseklikler[i]} satir): ${s.join(" | ")}`);
});
icerik.forEach((b, i) => { b.bolunur = eskiIcerik[i].bolunur; });
toplam 42 satir, 3 sutun, esit bolunme 14.00 satir
bolunmez bloklar: olcu-listesi(7), sekil(9)

--- column-fill: balance  (bulunan yukseklik: 18 satir) ---
sutun 1 (18 satir): giris-p(6) | yontem-p(5) | olcu-listesi(7)
sutun 2 (18 satir): ara-p(4) | sekil(9) | sonuc-p.1(5)
sutun 3 (6 satir): sonuc-p.2(3) | not-p(3)
en yuksek - en alcak fark: 12 satir

--- column-fill: auto, block-size: 12 satir ---
sutun 1 (11 satir): giris-p(6) | yontem-p(5)
sutun 2 (11 satir): olcu-listesi(7) | ara-p(4)
sutun 3 (12 satir): sekil(9) | sonuc-p.1(3)
sutun 4 (8 satir): sonuc-p.2(5) | not-p(3)  <- tanimli sutun sayisini asti

--- ayni icerik, hicbir blokta break-inside: avoid yok ---
bulunan yukseklik: 14 satir (bolunmez bloklarla: 18 satir)
sutun 1 (14 satir): giris-p(6) | yontem-p(5) | olcu-listesi.1(3)
sutun 2 (14 satir): olcu-listesi.2(4) | ara-p(4) | sekil.1(6)
sutun 3 (14 satir): sekil.2(3) | sonuc-p(8) | not-p(3)

Bu bir modeldir, tarayıcının çıktısı değil: satır sayıları varsayılmış, dengeleme araması en küçük yüksekliği bulacak biçimde yazılmıştır. Modelin gösterdiği kısıt gerçektir.

Üçüncü blok, bölünmenin serbest olduğu durumu veriyor: 42 satır üç sütuna 14, 14, 14 olarak dağılıyor. Eşit bölünmeden başka bir sonuç yok.

Birinci blokta iki blok bölünmez işaretli. Dengeleme, üçüncü bir sütun açmadan içeriği sığdırabilen en küçük yüksekliği arıyor ve 14 yerine 18 buluyor. Sonuçtaki dağılım 18, 18, 6; son sütun diğerlerinin üçte biri kadar dolu. Bölünmez bir blok, kendisinden küçük bir boşluğa sığmadığında bir sonraki sütunun başına atlar ve arkasında kullanılamayan boşluk bırakır. Dengeleme bu boşlukları geri kazanamaz.

İkinci blok auto değerini gösteriyor. Yükseklik dışarıdan verilmiş, sütunlar sırayla doluyor ve içerik üç sütuna sığmıyor: dördüncü bir sütun oluşuyor. Tanımlı sütun sayısını aşan bu sütunlara taşan sütun (overflow column) denir ve kutunun sınırının dışına çıkar. column-fill: auto bu yüzden yalnız yüksekliği denetlenen kutularda anlamlıdır.

Kırılma Denetimi

Metin sütunlar arasında bölündüğünde bazı bölünmeler istenmez: bir başlık sütunun en altında tek başına kalmamalı, bir tablo ortadan ikiye ayrılmamalıdır. Bunu bildiren özellik ailesi kırılma denetimidir.

break-inside: avoid bir kutunun içinden bölünmesini engeller. break-before: column ve break-after: column bir kutunun önünde ya da arkasında sütun kırılmasını zorlar. Bu bildirimler yalnız çok sütunlu düzende değil, sayfalanmış çıktıda (yazdırma) da aynı adlarla çalışır; ikisi de içeriğin parçalara ayrıldığı bağlamlardır.

İki bildirim daha satır düzeyinde çalışır. orphans bir parçanın sonunda bırakılacak en az satır sayısını, widows bir sonraki parçanın başında bulunacak en az satır sayısını verir. Varsayılan değer ikisi için de 2’dir: bir paragrafın son satırı, tek başına yeni bir sütunun başına düşmez.

Bu bildirimler istek bildirir, garanti vermez. İstek yerine getirildiğinde bir bedeli olduğu yukarıdaki hesapta görüldü: bölünmez blok sayısı arttıkça dengeleme bozulur. Kırılma denetimi seçici kullanılır; her bloğa avoid yazmak sütunları kullanılamaz hâle getirir.

Ayıraç ve Yayılma

İki bildirim daha görsel düzenlemeyi tamamlar.

column-rule, sütunların arasına bir çizgi çizer ve yazımı kenarlık kısayoluyla aynıdır: kalınlık, biçim, renk. Bu çizgi yer kaplamaz; sütun aralığının ortasına, ölçüye katılmadan çizilir. Aralık çizgiden darsa çizgi sütunların üzerine taşar.

column-span: all, bir ögenin bütün sütunları kesmesini sağlar. Böyle bir öge akışı ikiye böler: kendisinden önceki içerik sütunlara dağıtılır, öge tam genişlikte yerleşir, sonraki içerik yeniden sütunlara dağıtılır. İki grup ayrı ayrı dengelenir. Bir bölüm başlığını sütunların üzerinde tutmanın yolu budur.

/* duzen.css — 6. adım: istasyon yöntem notları */
.istasyon-notlari {
  columns: 18rem 3;
  column-gap: var(--aralik-1);
  column-rule: 1px solid var(--cizgi);
}

.istasyon-notlari > h3 {
  column-span: all;
  margin-block-start: 0;
}

.istasyon-notlari figure,
.istasyon-notlari table,
.istasyon-notlari li {
  break-inside: avoid;
}

.istasyon-notlari p {
  orphans: 3;
  widows: 3;
}

Bildirim kümesi küçük ama her satırın gerekçesi ayrıdır. columns sütun sayısını iki koşulun küçüğüne bağlar. column-rule ayrımı görünür kılar ve ölçüye karışmaz. column-span: all başlığı sütunların üzerinde tutar. break-inside: avoid yalnız bölünmesi anlamı bozan üç öge türüne yazılmıştır — paragraflara yazılmamıştır, çünkü paragrafın bölünmesi normaldir. orphans ve widows değerleri varsayılanın bir üstüne çekilerek tek ve iki satırlık artıklar engellenmiştir.

Nerede Uygun Değil

Çok sütunlu düzenin bir sınırı vardır ve okuma yönünden gelir. Sütunlar dikey doldurulur: okuyucu birinci sütunun altına iner, sonra ikinci sütunun başına döner. Bu, kâğıtta doğaldır çünkü sütunun tamamı aynı anda görünür. Ekranda sütun, görüntü alanından uzunsa okuyucu aşağı kaydırır, sonra yukarı geri kaydırır ve bir sonraki sütuna geçer.

Kural şudur: bir sütunun yüksekliği görüntü alanını aşıyorsa çok sütunlu düzen okumayı zorlaştırır. Kısa metinlerde — bir yöntem notu, bir tanım listesi, bir kaynakça — kazanç gerçektir; uzun bir makalede kayıptır.

İkinci sınır, sütunların belgede karşılığı olmamasıdır. Ekran okuyucu ve klavye odağı belge sırasını izler; sütunlar bu sırayı değiştirmediği için ek bir erişilebilirlik sorunu doğmaz. Ancak sütun içindeki bir bağlantıya odaklanıldığında sayfanın nereye kaydırılacağı, sütun yüksekliğine bağlı olarak beklenmedik olabilir.

Özet

  • Çok sütunlu düzen kutuyu değil akışı böler; tek bir paragraf iki sütuna parçalanabilir, sütunlar ise belgede öge karşılığı olmayan yapılardır.
  • Kullanılan sütun sayısı, yazılan column-count değeri ile kapsayıcıya sığan sütun sayısının küçüğüdür; kullanılan genişlik kalan alanın bu sayıya bölünmesiyle bulunur.
  • column-width bir hedef değil alt sınırdır: kullanılan genişlik bu değerin üstüne, tek sütunlu durumda iki katından fazlasına çıkabilir.
  • column-fill: balance sütun yüksekliklerini eşitlemeye çalışır; bölünmez bloklar dengelemeyi bozar ve son sütunun boş kalmasına yol açar.
  • column-span: all akışı ikiye böler ve iki bölüm ayrı dengelenir; column-rule sütun aralığına yer kaplamadan çizilir.
  • Bir sütunun yüksekliği görüntü alanını aşıyorsa okuyucu aşağı inip yukarı dönmek zorunda kalır; bu, düzenin uygun olmadığının ölçütüdür.

Sonraki Adım

Bu ders bir ölçütü sessizce kullandı: sütun genişliği kapsayıcıya sığmadığında sütun sayısı düşürüldü. Karar, bir ekran genişliği listesine değil, içeriğin okunabilir kaldığı en dar ölçüye dayanıyordu. Aynı akıl yürütme sayfanın tamamı için geçerlidir. Sonraki konu bu akıl yürütmeyi düzenin bütününe taşır: bir düzenin hangi genişlikte değişmesi gerektiği hangi ölçüyle bulunur ve bu karar neden ekran listelerinden değil içerikten türetilir?

İ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