Blok parçalara bölme, CSS blok düzeyinde bir kutuyu (bölüm veya paragraf gibi) bir bütün olarak parça kapsayıcısı adı verilen bir parça kapsayıcısının içine sığmadığında birden fazla parçaya bölme işlemidir. Parçalayıcı bir öğe değildir ancak çok sütunlu düzende bir sütunu veya sayfalı medyada bir sayfayı temsil eder.
Parçalanmanın gerçekleşmesi için içeriğin bir parçalama bağlamı içinde olması gerekir. Parçalanma bağlamı genellikle çok sütunlu bir kapsayıcı (içerik sütunlara bölünür) veya yazdırma işlemi (içerik sayfalara bölünür) tarafından oluşturulur. Çok sayıda satır içeren uzun bir paragrafın birden fazla parçaya bölünmesi gerekebilir. Böylece ilk satırlar ilk parçaya, kalan satırlar ise sonraki parçalara yerleştirilir.
Blok parçalara ayırma, iyi bilinen başka bir parçalara ayırma türüne benzer: satır parçalara ayırma (başka bir deyişle "satır bölme"). Birden fazla kelimeden oluşan (herhangi bir metin düğümü, <a>
öğesi vb.) ve satır sonlarına izin veren herhangi bir satır içi öğe birden fazla parçaya bölünebilir. Her bir parça farklı bir satır kutusuna yerleştirilir. Satır kutusu, sütunlar ve sayfalar için parçalayıcı eşdeğeri satır içi parçalamadır.
LayoutNG blok parçalanması
LayoutNGBlockFragmentation, LayoutNG için parçalama motorunun yeniden yazılmış halidir ve ilk olarak Chrome 102'de kullanıma sunulmuştur. Veri yapıları açısından bakıldığında, NG öncesi birden fazla veri yapısını, doğrudan parça ağacında temsil edilen NG parçaları ile değiştirdi.
Örneğin, artık yazarların başlıktan hemen sonra aralardan kaçınmasına olanak tanıyan "break-before" ve "break-sonra" CSS özelliklerinde "kaçınma" değerini destekliyoruz. Bir sayfada son öğe bir başlık olduğunda ve bölümün içeriği bir sonraki sayfada başladığında genellikle garip bir görünüm ortaya çıkar. Başlıktan önce ara vermeniz önerilir.
Chrome, parçalanma taşmasını da destekler. Böylece monolitik (kırılamaz olduğu varsayılır) içerik birden fazla sütuna bölünmez ve gölgeler ve dönüştürmeler gibi boya efektleri doğru şekilde uygulanır.
LayoutNG'de blok parçalama işlemi tamamlandı
Temel parçalanma (satır düzeni, yüzen öğeler ve akış dışı konumlandırma dahil olmak üzere blok kapsayıcılar) Chrome 102'de kullanıma sunulmuştur. Esnek ve ızgara parçalandırması Chrome 103'te, tablo parçalandırması ise Chrome 106'ta kullanıma sunulmuştur. Son olarak, Chrome 108'de yazdırma özelliği kullanıma sunuldu. Blok parçalama, düzen performansı için eski motora bağlı olan son özellikti.
Chrome 108'den itibaren eski motor artık sayfa düzenini oluşturmak için kullanılmamaktadır.
Ayrıca LayoutNG veri yapıları boyama ve isabet testi işlemlerini destekler ancak offsetLeft
ve offsetTop
gibi düzen bilgilerini okuyan JavaScript API'leri için bazı eski veri yapılarını kullanırız.
Her şeyi NG ile tasarlamak, yalnızca LayoutNG uygulamalarının olduğu (eski motor eşdeğeri olmayan) yeni özellikleri (ör. CSS kapsayıcı sorguları, ankraj konumlandırma, MathML ve özel düzen (Houdini)) uygulamayı ve yayınlamayı mümkün kılar. Kapsayıcı sorguları için bu özelliği biraz önce kullanıma sunduk ve geliştiricilere baskının henüz desteklenmediğine dair bir uyarı gönderdik.
LayoutNG'nin ilk bölümünü 2019'da kullanıma sunduk. Normal blok kapsayıcı düzeni, satır içi düzen, kayan öğeler ve akış dışı konumlandırmadan oluşan ancak esnek, ızgara veya tablolar için destek sağlanmıyor, blok parçalanma desteği de bulunmuyor. Esnek, ızgara, tablolar ve blok parçalanmasını içeren her şey için eski düzen motorunu kullanmaya geri döneriz. Bu durum, parçalanmış içerikteki blok, satır içi, yüzen ve akış dışı öğeler için bile geçerliydi. Gördüğünüz gibi, bu kadar karmaşık bir düzen motorunu yerinde yükseltmek çok hassas bir işlemdir.
Ayrıca, 2019'un ortalarına kadar LayoutNG blok parçalama düzeninin temel işlevlerinin çoğu zaten uygulanmıştı (bir işaretin arkasında). Peki, gönderim neden bu kadar uzun sürdü? Kısa cevap: Parçalanmanın, sistemin çeşitli eski bölümleriyle doğru bir şekilde bir arada bulunması gerekir. Bu bölümler, tüm bağımlılıklar yükseltilene kadar kaldırılamaz veya yükseltilemez.
Eski motor etkileşimi
Eski veri yapıları, düzen bilgilerini okuyan JavaScript API'lerinden sorumlu olmaya devam eder. Bu nedenle, verileri eski motora onun anlayacağı bir şekilde geri yazmamız gerekir. Bu, LayoutMultiColumnFlowThread gibi eski çok sütunlu veri yapılarının doğru şekilde güncellenmesini de içerir.
Eski motor yedek algılama ve işleme
İçerisinde LayoutNG blok parçalandırması tarafından henüz işlenemeyen içerikler olduğunda eski düzen motoruna geri dönmek zorunda kaldık. Gönderim sırasında, temel LayoutNG blok parçalanması (flex, ızgara, tablolar ve basılı olan her şey dahil) Bu, düzenleme ağacında nesneler oluşturmadan önce eski yedekleme ihtiyacını tespit etmemiz gerektiğinden özellikle zordu. Örneğin, çok sütunlu bir kapsayıcı üst öğesi olup olmadığını ve hangi DOM düğümlerinin biçimlendirme bağlamı olacağını bilmeden önce bunu tespit etmemiz gerekiyordu. Bu, mükemmel bir çözümü olmayan bir tavuk-yumurta problemidir, ancak tek hatalı davranışı yanlış pozitifler (gerçekte ihtiyaç duyulmadığında eskiye geri döner) olduğu sürece sorun değildir, çünkü bu düzen davranışındaki hatalar Chromium'da bulunan hatalardır, yeni olanlar değildir.
Ağaç boyama öncesi yürüyüş
Boya öncesi işlem, düzenlemeden sonra ancak boyamadan önce yaptığımız bir işlemdir. Temel zorluk, düzen nesnesi ağacında gezinmeye devam etmemiz gerektiği halde artık NG parçalarımız olduğu için bu sorunu nasıl çözeceğimizdir. Hem düzen nesnesini hem de NG fragment ağaçlarını aynı anda gezeriz. İki ağaç arasında eşleme yapmak kolay olmadığı için bu işlem oldukça karmaşıktır.
Düzen nesne ağacı yapısı, DOM ağacının yapısına çok benzese de parça ağacı, düzene bir giriş değil, düzenin bir çıktısıdır. Parça ağacı, satır içi parça (satır parçaları) ve blok parçalanması (sütun veya sayfa parçaları) dahil olmak üzere herhangi bir parçalanmanın etkisini gerçekten yansıtmanın yanı sıra, içeren blok ile bu parçayı içeren bloka sahip olan DOM alt öğeleri arasında doğrudan bir üst-alt ilişkisine de sahiptir. Örneğin, parça ağacında kesinlikle konumlandırılmış bir öğe tarafından oluşturulan parça, içeren blok parçasının doğrudan alt öğesidir. Üst öğe zincirinde, akış dışı konumlandırılmış alt öğe ile içeren blok arasında başka düğümler olsa bile bu parça, öğenin bulunduğu blok parçasının doğrudan alt öğesidir.
Parçalandırma içinde akış dışı konumlandırılmış bir öğe olduğunda bu durum daha da karmaşık olabilir. Çünkü bu durumda akış dışı parçalar, parçalandırıcının doğrudan alt öğeleri olur (CSS'nin kapsayıcı blok olarak düşündüğü öğenin alt öğesi değil). Bunun eski motorla bir arada var olması için çözülmesi gereken bir sorundu. LayoutNG, tüm modern düzen modlarını esnek bir şekilde desteklemek için tasarlandığından gelecekte bu kodu basitleştirebileceğiz.
Eski parçalama motoruyla ilgili sorunlar
Web'in daha önceki bir döneminde tasarlanan eski motorda, o zamanlar teknik olarak da olsa (basımı desteklemek için) parçalanma kavramı yoktur. Parçalara ayırma desteği, yalnızca üst kısımda sabitlenmiş (baskı) veya sonradan eklenmiş (çok sütunlu) bir özellikti.
Eski motor, parçalara ayrılabilir içeriği düzenlerken her şeyi genişliği bir sütunun veya sayfanın satır içi boyutu, yüksekliği ise içeriğini barındırmak için gereken kadar olan uzun bir şerit halinde düzenler. Bu uzun şerit sayfaya oluşturulmaz. Bunun yerine, sanal bir sayfaya oluşturulur ve daha sonra nihai görüntüleme için yeniden düzenlenir. Bu işlem, bir gazete makalesinin tamamını tek bir sütunda basıp ardından makaleyi makasla birden fazla sütuna kesmeye benzer. (Eskiden bazı gazeteler buna benzer teknikler kullanıyordu.)
Eski motor, şeritteki hayali bir sayfa veya sütun sınırını izler. Bu sayede, sınırın dışına taşmayan içeriği sonraki sayfaya veya sütuna kaydırabilirsiniz. Örneğin, bir satırın yalnızca üst yarısı, motorun mevcut sayfa olduğunu düşündüğü yere sığıyorsa motor, satırın üst yarısını bir sonraki sayfanın üst kısmının olduğu varsayılan konuma itmek için bir "sayfalandırma desteği" ekler. Ardından, gerçek parçalara ayırma işleminin çoğu ("makasla kesme ve yerleştirme"), sayfa düzeninden sonra, ön boyama ve boyama sırasında gerçekleşir. Bu işlemde, uzun içerik şeridinin sayfalara veya sütunlara bölünmesi (bölümlerin kırpılması ve çevrilmesi) gerekir. Bu durum, dönüştürme ve göreli konumlandırma işlemlerinin parçalanmanın sonrasında uygulanması (özelliğin gerektirdiği şey budur) gibi bazı işlemlerin yapılmasını imkansız hale getiriyordu. Ayrıca, eski motorda tablo parçalara ayırma için bazı destekler olsa da esnek veya ızgara parçalara ayırma desteği hiç yoktur.
Burada, makas, yerleşim ve tutkal kullanmadan önce eski motorda üç sütunlu düzenin dahili olarak nasıl temsil edildiğini görebilirsiniz (yüksekliği belirli bir değere sahip olduğumuz için yalnızca dört satır sığdır, ancak alt kısımda biraz fazla alan vardır):
Eski düzen motoru, düzen sırasında içeriği parçalara ayırmadığı için göreli konumlandırma ve dönüştürme işlemlerinin yanlış uygulanması ve kutu gölgelerinin sütun kenarlarında kırpılması gibi birçok garip yapı ortaya çıkar.
text-shadow içeren bir örneği aşağıda bulabilirsiniz:
Eski motor, bu konuda iyi performans göstermez:
İlk sütundaki satırdaki metin gölgesinin nasıl kırpıldığını ve bunun yerine ikinci sütunun üst kısmına yerleştirildiğini görüyor musunuz? Bunun nedeni, eski düzen motorunun parçalanmayı anlamamasıdır.
Aşağıdaki gibi görünecektir:
Ardından, dönüşümler ve box-shadow ile bunu biraz daha karmaşık hale getirelim. Eski motorda nasıl yanlış kırpma ve sütun taşması olduğuna dikkat edin. Bunun nedeni, dönüşümlerin özellik olarak bir düzen sonrası, parçalama sonrası efekt olarak uygulanması gerekir. LayoutNG parçalandırması ile her ikisi de düzgün çalışır. Bu, bir süre boyunca iyi parçalama desteğine sahip olan ve bu alandaki çoğu test oradan geçen Firefox ile birlikte çalışma oranını artırdı.
Eski motor, yüksek ve tek parça içeriklerle ilgili de sorunlara yol açar. Birden fazla parçaya bölünmeye uygun olmayan içerikler monolitik olarak kabul edilir. Taşma kaydırmalı öğeler monolitiktir, çünkü kullanıcıların dikdörtgen olmayan bir bölgede kaydırması mantıklı değildir. Satır kutuları ve resimler de tek parça içeriklere örnek gösterilebilir. Aşağıda bununla ilgili bir örnek verilmiştir:
Monolitik içerik parçası bir sütunun içine sığmayacak kadar uzunsa eski motor bu parçayı acımasızca dilimlere ayırır (kaydırılabilir kapsayıcıyı kaydırmaya çalışırken çok "ilginç" bir davranışa yol açar):
İlk sütunu taşmasına izin vermek yerine (LayoutNG blok parçalanmasında olduğu gibi):
Eski motor, zorunlu araları destekler. Örneğin, <div style="break-before:page;">
, DIV öğesinin önüne bir sayfa sonu ekler. Ancak en uygun zorunlu olmayan araları bulmak için sınırlı bir desteğe sahiptir. break-inside:avoid
ile yetimler ve dullar desteklenir ancak break-before:avoid
aracılığıyla istenmesi durumunda, bloklar arasında boşluklardan kaçınmak için destek sunulmaz. Aşağıdaki örneğe bakın:
Burada, #multicol
öğesi için her sütunda 5 satırlık bir alan vardır (çünkü öğenin yüksekliği 100 piksel, çizgi yüksekliği 20 pikseldir). Bu nedenle, #firstchild
öğesinin tamamı ilk sütuna sığabilir. Ancak kardeşi #secondchild
'te break-before:avoid var. Bu, içeriğin aralarında ara verilmemesini istediği anlamına gelir. widows
değeri 2 olduğundan, tüm aradan kaçınma isteklerini yerine getirmek amacıyla ikinci sütuna 2 #firstchild
satırını aktarmamız gerekir. Chromium, bu özellik kombinasyonunu tam olarak destekleyen ilk tarayıcı motorudur.
NG parçalanmasının işleyiş şekli
NG düzen motoru, genellikle CSS kutu ağacını derinlik öncelikli olarak dolaşarak belgeyi düzenler. Bir düğümün tüm alt öğeleri düzenlendiğinde, NGPhysicalFragment oluşturarak ve üst öğe düzen algoritmasına dönerek söz konusu düğümün düzeni tamamlanabilir. Bu algoritma, parçayı alt parça listesine ekler ve tüm alt parçalar tamamlandığında, tüm alt parçalarını içeren bir parça oluşturur. Bu yöntemle, dokümanın tamamı için bir parça ağacı oluşturur. Ancak bu, fazla basitleştirilmiş bir açıklamadır: Örneğin, akış dışında konumlandırılmış öğelerin düzenlenebilmesi için DOM ağacında bulundukları yerden kapsayıcı bloklarına doğru yukarı doğru hareket etmeleri gerekir. Basitlik açısından bu gelişmiş ayrıntıyı göz ardı ediyoruz.
LayoutNG, CSS kutusunun yanı sıra bir düzen algoritmasına kısıtlama alanı sağlar. Bu sayede algoritmaya, sayfa düzeni için kullanılabilecek alan, yeni bir biçimlendirme bağlamı oluşturulup oluşturulmadığı ve önceki içerikten elde edilen ara kenar boşluğu daraltma sonuçları gibi bilgiler sağlanır. Kısıtlama alanı, parçalayıcının düzenlenmiş blok boyutunu ve mevcut blok ofsetini de bilir. Bu, nerede son verileceğini gösterir.
Blok parçalanması söz konusu olduğunda, alt öğelerin düzeninin bir ara verme noktasında durması gerekir. Sayfa veya sütunda boş alan kalmaması ya da zorunlu bir ayrılma, ayrılma nedenleri arasındadır. Ardından, ziyaret ettiğimiz düğümler için parçalar oluşturur ve parçalama bağlam köküne (çoklu sütun kapsayıcısı veya baskı durumunda belge kökü) kadar geri döneriz. Ardından, parçalanma bağlamı kökünde yeni bir parçalayıcı için hazırlanır ve ağaca tekrar iner, aradan önce kaldığımız yerden devam ederiz.
Aradan sonra düzeni devam ettirme olanağı sağlayan önemli veri yapısına NGBlockBreakToken adı verilir. Sonraki parçalayıcıda düzeni doğru şekilde devam ettirmek için gereken tüm bilgileri içerir. NGBlockBreakToken, bir düğümle ilişkilendirilir ve devam edilmesi gereken her düğümün temsil edilmesi için bir NGBlockBreakToken ağacı oluşturur. İçinde kırılan düğümler için oluşturulan NGPhysicalBoxFragment bölümüne bir NGBlockBreakToken eklenir. Ara karakter jetonları, ara karakter jetonu ağacı oluşturacak şekilde üst öğelere dağıtılır. Bir düğümün öncesinde (içinde değil) ara vermemiz gerekirse hiçbir parça oluşturulmaz ancak üst düğümün, düğüm için bir "break-before" ara verme jetonu oluşturması gerekir. Böylece, sonraki parça kapsayıcısında düğüm ağacında aynı konuma geldiğimizde ara vermeyi başlatabiliriz.
Parçalayıcı alanında yer kalmadığında (zorunlu olmayan ara) veya zorunlu ara istendiğinde aralar eklenir.
Spesifikasyonda, zorunlu olmayan en uygun aralar için kurallar vardır ve tam olarak yer kalmadığı yerde ara eklemek her zaman doğru bir işlem değildir. Örneğin, break-before
gibi çeşitli CSS özellikleri, ara yeri seçimini etkiler.
Sayfa düzeni sırasında zorunlu olmayan aralar spesifikasyon bölümünü doğru şekilde uygulamak için olası iyi kesme noktalarını takip etmemiz gerekir. Bu kayıt, aradan kaçma isteklerini ihlal edeceğimiz bir noktada alanımız tükenirse (örneğin, break-before:avoid
veya orphans:7
) geri dönüp bulunan mümkün olan en son ayrılma noktasını kullanabileceğimiz anlamına gelir. Olası her ayrılma noktasına, "bunu yalnızca son çare olarak yap"dan "kırılmak için mükemmel bir yer"e kadar değişen bir puan verilir. Bir ara konumunun puanı "kusursuz" ise bu, orayı ihlal ettiğimizde hiçbir çiğnenme kuralının ihlal edilmeyeceğini ifade eder. Bu puanı tam olarak kapladığı yerde alanmız tükendiğinde daha iyi bir şey bulmak için geriye bakmamıza gerek yoktur. Skor "son çare" ise, ayrılma noktası geçerli bir nokta bile değildir, ancak daha iyi bir şey bulamazsak parçalayıcı taşmasını önlemek için ayrılma noktası yine de bozulabilir.
Geçerli kesme noktaları genellikle yalnızca kardeşler (satır kutuları veya bloklar) arasında bulunur, örneğin bir üst öğe ile ilk alt öğesi arasında bulunmaz (C sınıfı kesme noktaları istisnadır ancak burada bunlar hakkında konuşmamız gerekmez). Örneğin, break-before:avoid içeren bir blok kardeşinden önce geçerli bir kesme noktası var ancak bu nokta "mükemmel" ile "son çare" arasında bir yerdedir.
Sayfa düzeni sırasında, NGEarlyBreak adlı bir yapıda şimdiye kadar bulunan en iyi kesme noktasını izleriz. Erken ara verme, bir blok düğümünün önünde veya içinde ya da bir satırın (blok kapsayıcı satırı veya esnek satır) önündeki olası bir kesme noktasıdır. En iyi kesme noktasının, yer kalmadığında daha önce gözden kaçırdığımız bir yerin derinliklerinde olması ihtimaline karşı NGEarlyBreak nesnelerinden bir zincir veya yol oluşturabiliriz. Aşağıda bununla ilgili bir örnek verilmiştir:
Bu durumda, #second
öğesinden hemen önce yerimiz bitiyor ancak "break-before:avoid" değeri olduğu için "break avoid'i ihlal ediyor" şeklinde bir ara verme yeri puanı alıyoruz. Bu noktada, "#outer
içinde > #middle
içinde > #inner
içinde > "3. satır"dan önce" şeklinde bir NGEarlyBreak zincirimiz var. "Mükemmel" değerini aldığı için burada ara vermeyi tercih ederiz. Bu nedenle, #inner'daki "3. satır"dan önce ara verebilmemiz için layout'ı geri dönüp #outer'ın başından yeniden çalıştırmamız (ve bu kez bulduğumuz NGEarlyBreak'i iletmemiz) gerekir. ("Satır 3"ten önce bölünür, böylece geri kalan 4 satır bir sonraki parçalayıcıda yer alır ve widows:4
onurlandırmak için kullanılır.)
Algoritma, kuralları doğru sırayla bırakarak her zaman mümkün olan en iyi kesme noktasında (özellikte tanımlandığı şekilde) duraklayacak şekilde tasarlanmıştır. Tüm kurallar karşılanamazsa algoritma duraklamaz. Parçalama akışı başına en fazla bir kez yeniden düzenlememiz gerektiğini unutmayın. İkinci düzen geçişine geldiğimizde, en iyi ara konumu düzen algoritmalarına geçirilmiştir. Bu, ilk düzen geçişinde keşfedilen ve o turda düzen çıktısının bir parçası olarak sağlanan ara konumudur. İkinci düzen geçişinde, yerimiz dolana kadar düzen oluşturmayız. Aslında yerimizin dolması beklenmez (bu aslında bir hata olur). Çünkü gereksiz yere kuralları ihlal etmemek için erken bir ara eklemek üzere süper güzel (mevcut olan en güzel) bir yer sağlanmıştır. Bu noktada konuyu toparlayıp ara veriyoruz.
Bu nedenle, parçalayıcı taşmasını önlemeye yardımcı olması durumunda bazen, aradan kaçınma isteklerinden bazılarını ihlal etmemiz gerekebilir. Örneğin:
Burada, #second
'ten hemen önce yerimiz doldu ancak "break-before:avoid" değeri var. Bu, son örnekte olduğu gibi "break avoid'i ihlal ediyor" olarak çevrilir. Ayrıca "boş satır ve tek satırdan oluşan paragraf ihlali" (#first
içinde > "2. satır"dan önce) içeren bir NGEarlyBreak'imiz de var. Bu, mükemmel olmasa da "boş satır ve tek satırdan oluşan paragraf ihlali"nden daha iyidir. Bu nedenle, "2. satır"dan önce ara vereceğiz ve tek satırda kalan satırlar / tek satırdan başlayan satırlar isteğini ihlal edeceğiz. Bu spesifikasyon, 4.4. Zorunlu olmayan aralar: Parçalayıcı taşmasını önlemek için yeterli kesme noktamız yoksa önce hangi ara verme kurallarının yoksayıldığını tanımlar.
Sonuç
LayoutNG blok parçalama projesinin işlevsel hedefi, eski motorun desteklediği her şeyin ve hata düzeltmelerinin dışında mümkün olduğunca az bir şekilde LayoutNG mimarisini destekleyen uygulamasını sağlamaktı. Bunun başlıca istisnası, daha iyi ara verme desteğidir (örneğin, break-before:avoid
). Bu, parçalama motorunun temel bir parçası olduğundan, daha sonra eklenmesi başka bir yeniden yazma anlamına geleceği için baştan eklenmelidir.
LayoutNG blok parçalandırması tamamlandığından, yazdırma sırasında karışık sayfa boyutlarını destekleme, @page
yazdırma sırasında kenar boşluğu kutuları box-decoration-break:clone
ve daha fazlası gibi yeni işlevler eklemeye başlayabiliriz. Genel olarak LayoutNG'de olduğu gibi, yeni sistemin hata oranının ve bakım yükünün zaman içinde önemli ölçüde azalmasını bekliyoruz.
Teşekkür ederiz
- Güzel "el yapımı ekran görüntüsü" için Una Kravets
- Düzeltme, geri bildirim ve öneriler için Chris Harrelson
- Geri bildirim ve önerilerinizi Philip Jägenstedt ile paylaşın.
- Düzenleme ve ilk çok sütunlu örnek şekil için Rachel Andrew.