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-countdeğ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-widthbir 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: balancesü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: allakışı ikiye böler ve iki bölüm ayrı dengelenir;column-rulesü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.