Fragmentasi blok adalah pemisahan kotak tingkat blok CSS (seperti bagian atau paragraf) menjadi beberapa fragmen jika tidak sesuai secara keseluruhan di dalam satu penampung fragmen, yang disebut fragmentainer. Fragmentainer bukan elemen, tetapi mewakili kolom dalam tata letak multi-kolom, atau halaman dalam media yang dibagi-bagi.
Agar fragmentasi terjadi, konten harus berada di dalam konteks fragmentasi. Konteks fragmentasi biasanya dibuat oleh penampung multikolom (konten dibagi menjadi kolom) atau saat mencetak (konten dibagi menjadi halaman). Paragraf panjang dengan banyak baris mungkin perlu dibagi menjadi beberapa fragmen, sehingga baris pertama ditempatkan di fragmen pertama, dan baris yang tersisa ditempatkan di fragmen berikutnya.

Fragmentasi blok analog dengan jenis fragmentasi lain yang terkenal: fragmentasi baris, atau dikenal sebagai "pemisahan baris". Setiap elemen inline yang terdiri dari lebih dari satu kata (node teks apa pun, elemen <a>
apa pun, dan sebagainya), dan memungkinkan pemisahan baris, dapat dibagi menjadi beberapa fragmen. Setiap fragmen ditempatkan ke dalam kotak baris yang berbeda. Kotak baris adalah fragmentasi inline yang setara dengan fragmentainer untuk kolom dan halaman.
Fragmentasi blok LayoutNG
LayoutNGBlockFragmentation adalah penulisan ulang mesin fragmentasi untuk LayoutNG, yang awalnya dikirimkan di Chrome 102. Dalam hal struktur data, versi ini mengganti beberapa struktur data pra-NG dengan fragmen NG yang direpresentasikan langsung dalam hierarki fragmen.
Misalnya, kini kami mendukung nilai'avoid' untuk properti CSS 'break-before' dan 'break-after', yang memungkinkan penulis menghindari jeda tepat setelah header. Hal ini sering kali terlihat canggung jika hal terakhir di halaman adalah header, sementara konten bagian dimulai di halaman berikutnya. Sebaiknya jeda sebelum header.

Chrome juga mendukung overflow fragmentasi, sehingga konten monolitik (yang seharusnya tidak dapat dihancurkan) tidak dipotong menjadi beberapa kolom, dan efek cat seperti bayangan dan transformasi diterapkan dengan benar.
Fragmentasi blok di LayoutNG kini telah selesai
Fragmentasi inti (penampung blok, termasuk tata letak baris, float, dan pemosisian di luar alur) dikirimkan di Chrome 102. Fragmentasi fleksibel dan petak dikirimkan di Chrome 103, dan fragmentasi tabel dikirimkan di Chrome 106. Terakhir, pencetakan dikirimkan di Chrome 108. Fragmentasi blok adalah fitur terakhir yang bergantung pada mesin lama untuk melakukan tata letak.
Mulai Chrome 108, mesin lama tidak lagi digunakan untuk melakukan tata letak.
Selain itu, struktur data LayoutNG mendukung proses menggambar dan pengujian hit, tetapi kami mengandalkan beberapa struktur data lama untuk JavaScript API yang membaca informasi tata letak, seperti offsetLeft
dan offsetTop
.
Menata semuanya dengan NG akan memungkinkan penerapan dan pengiriman fitur baru yang hanya memiliki implementasi LayoutNG (dan tidak ada mesin lama), seperti kueri penampung CSS, pemosisian anchor, MathML, dan tata letak kustom (Houdini). Untuk kueri penampung, kami mengirimkannya sedikit lebih awal, dengan peringatan kepada developer bahwa pencetakan belum didukung.
Kami merilis bagian pertama LayoutNG pada tahun 2019, yang terdiri dari tata letak penampung blok reguler, tata letak inline, float, dan pemosisian di luar alur, tetapi tidak ada dukungan untuk flex, petak, atau tabel, dan tidak ada dukungan fragmentasi blok sama sekali. Kita akan kembali menggunakan mesin tata letak lama untuk flex, petak, tabel, serta apa pun yang melibatkan fragmentasi blok. Hal ini berlaku bahkan untuk elemen blok, inline, mengambang, dan di luar alur dalam konten yang terfragmentasi—seperti yang dapat Anda lihat, mengupgrade mesin tata letak yang kompleks di tempat adalah hal yang sangat rumit.
Selain itu, pada pertengahan 2019, sebagian besar fungsi inti tata letak fragmentasi blok LayoutNG telah diimplementasikan (di balik tanda). Jadi, mengapa perlu waktu lama untuk mengirimnya? Jawaban singkatnya adalah: fragmentasi harus berdampingan dengan benar dengan berbagai bagian lama sistem, yang tidak dapat dihapus atau diupgrade hingga semua dependensi diupgrade.
Interaksi mesin lama
Struktur data lama masih bertanggung jawab atas API JavaScript yang membaca informasi tata letak, sehingga kita perlu menulis ulang data ke mesin lama dengan cara yang dipahaminya. Hal ini mencakup pembaruan struktur data multi-kolom lama, seperti LayoutMultiColumnFlowThread, dengan benar.
Deteksi dan penanganan penggantian mesin lama
Kami harus kembali ke mesin tata letak lama jika ada konten di dalamnya yang belum dapat ditangani oleh fragmentasi blok LayoutNG. Pada saat mengirimkan fragmentasi blok LayoutNG inti, yang mencakup fleksibel, petak, tabel, dan apa pun yang dicetak. Hal ini sangat rumit karena kita perlu mendeteksi kebutuhan penggantian lama sebelum membuat objek dalam hierarki tata letak. Misalnya, kita perlu mendeteksi sebelum mengetahui apakah ada ancestor penampung multi-kolom, dan sebelum mengetahui node DOM mana yang akan menjadi konteks pemformatan atau tidak. Ini adalah masalah ayam dan telur yang tidak memiliki solusi yang sempurna, tetapi selama satu-satunya perilaku yang salah adalah positif palsu (fallback ke versi lama saat sebenarnya tidak diperlukan), tidak masalah, karena bug dalam perilaku tata letak tersebut adalah bug yang sudah dimiliki Chromium, bukan bug baru.
Proses berjalan pada hierarki pohon sebelum proses paint
Pra-pengecatan adalah sesuatu yang kita lakukan setelah tata letak, tetapi sebelum mengecat. Tantangan utamanya adalah kita masih perlu menelusuri hierarki objek tata letak, tetapi sekarang kita memiliki fragmen NG—jadi, bagaimana cara mengatasinya? Kita akan menelusuri objek tata letak dan hierarki fragmen NG secara bersamaan. Hal ini cukup rumit, karena pemetaan antara kedua hierarki bukanlah hal yang mudah.
Meskipun struktur hierarki objek tata letak sangat mirip dengan hierarki DOM, hierarki fragmen adalah output tata letak, bukan input untuk tata letak. Selain benar-benar mencerminkan efek fragmentasi apa pun, termasuk fragmentasi inline (fragmen baris) dan fragmentasi blok (fragmen kolom atau halaman), hierarki fragmen juga memiliki hubungan induk-turunan langsung antara blok penampung dan turunan DOM yang memiliki fragmen tersebut sebagai blok penampung. Misalnya, dalam hierarki fragmen, fragmen yang dihasilkan oleh elemen yang diposisikan secara absolut adalah turunan langsung dari fragmen blok penampung, meskipun ada node lain dalam rantai leluhur antara turunan yang diposisikan di luar alur dan blok penampung.
Hal ini dapat menjadi lebih rumit jika ada elemen yang diposisikan di luar alur di dalam fragmentasi, karena fragmen di luar alur akan menjadi turunan langsung dari fragmentainer (dan bukan turunan dari apa yang dianggap CSS sebagai blok penampung). Ini adalah masalah yang harus diselesaikan agar dapat berdampingan dengan mesin lama. Di masa mendatang, kita akan dapat menyederhanakan kode ini, karena LayoutNG dirancang untuk mendukung semua mode tata letak modern secara fleksibel.
Masalah pada mesin fragmentasi lama
Mesin lama, yang dirancang pada era web sebelumnya, tidak benar-benar memiliki konsep fragmentasi, meskipun fragmentasi secara teknis juga ada pada saat itu (untuk mendukung pencetakan). Dukungan fragmentasi hanyalah sesuatu yang ditambahkan di bagian atas (pencetakan) atau ditambahkan (multi-kolom).
Saat menata konten yang dapat difragmentasi, mesin lama menata semuanya ke dalam strip tinggi yang lebarnya adalah ukuran inline kolom atau halaman, dan tingginya setinggi yang diperlukan untuk memuat kontennya. Strip tinggi ini tidak dirender ke halaman—anggaplah sebagai rendering ke halaman virtual yang kemudian disusun ulang untuk tampilan akhir. Secara konsep, hal ini mirip dengan mencetak seluruh artikel koran kertas ke dalam satu kolom, lalu menggunakan gunting untuk memotongnya menjadi beberapa bagian sebagai langkah kedua. (Dulu, beberapa surat kabar benar-benar menggunakan teknik yang mirip dengan ini!)
Mesin lama melacak batas kolom atau halaman imajiner di strip. Hal ini memungkinkannya mendorong konten yang tidak sesuai dengan batas ke halaman atau kolom berikutnya. Misalnya, jika hanya separuh bagian atas baris yang sesuai dengan yang dianggap mesin sebagai halaman saat ini, mesin akan menyisipkan "strut penomoran halaman" untuk mendorongnya ke bawah ke posisi yang dianggap mesin sebagai bagian atas halaman berikutnya. Kemudian, sebagian besar pekerjaan fragmentasi yang sebenarnya ("pemotongan dengan gunting dan penempatan") terjadi setelah tata letak selama pra-proses gambar dan proses gambar, dengan memotong strip konten yang tinggi menjadi halaman atau kolom (dengan memangkas dan menerjemahkan bagian). Hal ini membuat beberapa hal pada dasarnya tidak mungkin, seperti menerapkan transformasi dan pemosisian relatif setelah fragmentasi (yang diperlukan spesifikasi). Selain itu, meskipun ada beberapa dukungan untuk fragmentasi tabel di mesin lama, tidak ada dukungan fragmentasi fleksibel atau petak sama sekali.
Berikut adalah ilustrasi cara tata letak tiga kolom direpresentasikan secara internal di mesin lama, sebelum menggunakan gunting, penempatan, dan lem (kita memiliki tinggi yang ditentukan, sehingga hanya empat baris yang sesuai, tetapi ada beberapa ruang yang berlebih di bagian bawah):

Karena mesin tata letak lama tidak benar-benar memfragmentasi konten selama tata letak, ada banyak artefak aneh, seperti pemosisian relatif dan transformasi yang diterapkan secara salah, serta bayangan kotak yang terpotong di tepi kolom.
Berikut adalah contoh dengan text-shadow:
Mesin lama tidak menangani hal ini dengan baik:

Apakah Anda melihat bagaimana text-shadow dari baris di kolom pertama terpotong, dan ditempatkan di bagian atas kolom kedua? Hal ini karena mesin tata letak lama tidak memahami fragmentasi.
Hasilnya akan terlihat seperti berikut:
Selanjutnya, mari kita buat sedikit lebih rumit, dengan transformasi dan box-shadow. Perhatikan bahwa di mesin lama, ada pemotongan dan kebocoran kolom yang salah. Hal ini karena transformasi menurut spesifikasi seharusnya diterapkan sebagai efek pasca-tata letak, pasca-fragmentasi. Dengan fragmentasi LayoutNG, keduanya berfungsi dengan benar. Hal ini meningkatkan interop dengan Firefox, yang telah memiliki dukungan fragmentasi yang baik selama beberapa waktu dengan sebagian besar pengujian di area ini juga lulus di sana.
Mesin lama juga memiliki masalah dengan konten monolitik yang tinggi. Konten bersifat monolitik jika tidak memenuhi syarat untuk dipecah menjadi beberapa fragmen. Elemen dengan scroll tambahan bersifat monolitik, karena pengguna tidak akan merasa nyaman untuk men-scroll di wilayah non-persegi panjang. Kotak garis dan gambar adalah contoh lain dari konten monolitik. Berikut contohnya:
Jika potongan konten monolitik terlalu tinggi untuk muat di dalam kolom, mesin lama akan memotongnya secara brutal (menyebabkan perilaku yang sangat "menarik" saat mencoba men-scroll penampung yang dapat di-scroll):
Daripada membiarkannya meluap ke kolom pertama (seperti yang dilakukan dengan fragmentasi blok LayoutNG):
Mesin lama mendukung jeda paksa. Misalnya, <div style="break-before:page;">
akan menyisipkan batas halaman sebelum DIV. Namun, <div style="break-before:page;">
hanya memiliki dukungan terbatas untuk menemukan jeda tidak dipaksakan yang optimal. Format ini mendukung break-inside:avoid
dan orphans and widows, tetapi tidak ada dukungan untuk menghindari jeda di antara blok, misalnya jika diminta melalui break-before:avoid
. Perhatikan contoh ini:
Di sini, elemen #multicol
memiliki ruang untuk 5 baris di setiap kolom (karena tingginya 100 piksel, dan tinggi baris adalah 20 piksel), sehingga semua #firstchild
dapat muat di kolom pertama. Namun, saudaranya #secondchild
memiliki break-before:avoid, yang berarti konten tidak ingin ada jeda di antara keduanya. Karena nilai widows
adalah 2, kita perlu mendorong 2 baris #firstchild
ke kolom kedua, untuk memenuhi semua permintaan penghindaran jeda. Chromium adalah mesin browser pertama yang sepenuhnya mendukung kombinasi fitur ini.
Cara kerja fragmentasi NG
Mesin tata letak NG umumnya menata letak dokumen dengan menelusuri hierarki kotak CSS secara mendalam. Saat semua turunan node ditata, tata letak node tersebut dapat diselesaikan, dengan menghasilkan NGPhysicalFragment dan kembali ke algoritma tata letak induk. Algoritma tersebut menambahkan fragmen ke daftar fragmen turunannya, dan, setelah semua turunan selesai, membuat fragmen untuk dirinya sendiri dengan semua fragmen turunannya di dalamnya. Dengan metode ini, hierarki fragmen akan dibuat untuk seluruh dokumen. Namun, ini adalah penyederhanaan yang berlebihan: misalnya, elemen yang diposisikan di luar alur harus muncul dari tempatnya berada di hierarki DOM ke blok penampung sebelum dapat ditata. Saya mengabaikan detail lanjutan ini di sini demi kemudahan.
Bersama dengan kotak CSS itu sendiri, LayoutNG menyediakan ruang batasan ke algoritma tata letak. Hal ini memberi algoritma informasi seperti ruang yang tersedia untuk tata letak, apakah konteks pemformatan baru dibuat, dan hasil penyingkatan margin perantara dari konten sebelumnya. Ruang batasan juga mengetahui ukuran blok fragmentainer yang ditata, dan offset blok saat ini ke dalamnya. Ini menunjukkan tempat untuk berhenti.
Jika fragmentasi blok terlibat, tata letak turunan harus berhenti pada jeda. Alasan pemisahan baris mencakup kehabisan ruang di halaman atau kolom, atau pemisahan baris paksa. Kemudian, kita membuat fragmen untuk node yang kita kunjungi, dan menampilkannya hingga ke root konteks fragmentasi (penampung multikolom, atau, jika dicetak, root dokumen). Kemudian, di root konteks fragmentasi, kita bersiap untuk fragmentainer baru, dan turun ke hierarki lagi, melanjutkan dari tempat kita berhenti sebelum jeda.
Struktur data penting untuk menyediakan cara melanjutkan tata letak setelah jeda disebut NGBlockBreakToken. Ini berisi semua informasi yang diperlukan untuk melanjutkan tata letak dengan benar di fragmentainer berikutnya. NGBlockBreakToken dikaitkan dengan node, dan membentuk hierarki NGBlockBreakToken, sehingga setiap node yang perlu dilanjutkan akan direpresentasikan. NGBlockBreakToken dilampirkan ke NGPhysicalBoxFragment yang dihasilkan untuk node yang rusak di dalamnya. Token jeda disebarkan ke induk, membentuk hierarki token jeda. Jika kita perlu membuat jeda sebelum node (bukan di dalamnya), tidak akan ada fragmen yang dihasilkan, tetapi node induk masih perlu membuat token jeda "break-before" untuk node, sehingga kita dapat mulai menatanya saat mencapai posisi yang sama di hierarki node di fragmentainer berikutnya.
Jeda disisipkan saat ruang fragmentainer habis (jeda tidak dipaksa), atau saat jeda dipaksa diminta.
Ada aturan dalam spesifikasi untuk jeda yang tidak dipaksakan yang optimal dan hanya menyisipkan jeda tepat di tempat kita kehabisan ruang bukanlah hal yang tepat untuk dilakukan. Misalnya, ada berbagai properti CSS seperti break-before
yang memengaruhi pilihan lokasi jeda.
Selama tata letak, untuk menerapkan bagian spesifikasi jeda tidak dipaksakan dengan benar, kita perlu melacak titik henti sementara yang mungkin baik. Data ini berarti kita dapat kembali dan menggunakan titik henti sementara terbaik terakhir yang ditemukan, jika kita kehabisan ruang pada titik saat kita melanggar permintaan penghindaran jeda (misalnya, break-before:avoid
atau orphans:7
). Setiap titik henti sementara yang mungkin diberikan skor, mulai dari "hanya lakukan ini sebagai upaya terakhir" hingga "tempat yang sempurna untuk jeda", dengan beberapa nilai di antaranya. Jika lokasi jeda mendapatkan skor "sempurna", artinya tidak ada aturan jeda yang akan dilanggar jika kita berhenti di sana (dan jika kita mendapatkan skor ini tepat pada saat kita kehabisan ruang, tidak perlu mencari lagi yang lebih baik). Jika skornya adalah "last-resort", titik henti sementara bahkan tidak valid, tetapi kita mungkin masih berhenti di sana jika tidak menemukan yang lebih baik, untuk menghindari overflow fragmentainer.
Titik henti sementara yang valid umumnya hanya terjadi di antara saudara (kotak baris atau blok), dan bukan, misalnya, antara induk dan turunan pertamanya (titik henti sementara class C adalah pengecualian, tetapi kita tidak perlu membahasnya di sini). Misalnya, ada titik henti sementara yang valid sebelum saudara blok dengan break-before:avoid, tetapi titik henti sementara tersebut berada di antara "sempurna" dan "upaya terakhir".
Selama tata letak, kita melacak titik henti sementara terbaik yang ditemukan sejauh ini dalam struktur yang disebut NGEarlyBreak. Jeda awal adalah kemungkinan titik henti sementara sebelum atau di dalam node blok, atau sebelum baris (baik baris penampung blok, maupun baris fleksibel). Kita dapat membentuk rantai atau jalur objek NGEarlyBreak, jika titik henti sementara terbaik berada di suatu tempat di dalam sesuatu yang kita lewati sebelumnya saat kehabisan ruang. Berikut contohnya:
Dalam hal ini, kita kehabisan ruang tepat sebelum #second
, tetapi memiliki "break-before:avoid", yang mendapatkan skor lokasi jeda "melanggar jeda hindari". Pada titik tersebut, kita memiliki rantai NGEarlyBreak "inside #outer
> inside #middle
> inside #inner
> before "line 3"', dengan "perfect", jadi sebaiknya kita berhenti di sana. Jadi, kita perlu menampilkan dan menjalankan ulang tata letak dari awal #outer (dan kali ini meneruskan NGEarlyBreak yang kita temukan), sehingga kita dapat berhenti sebelum "baris 3" di #inner. (Kita berhenti sebelum "baris 3", sehingga 4 baris yang tersisa berakhir di fragmentainer berikutnya, dan untuk mematuhi widows:4
.)
Algoritme dirancang untuk selalu berhenti pada titik henti sementara terbaik—seperti yang ditentukan dalam spesifikasi—dengan menghapus aturan dalam urutan yang benar, jika tidak semuanya dapat dipenuhi. Perhatikan bahwa kita hanya perlu menata ulang maksimal sekali per alur fragmentasi. Pada saat kita berada di tahap tata letak kedua, lokasi jeda terbaik telah diteruskan ke algoritma tata letak, ini adalah lokasi jeda yang ditemukan di tahap tata letak pertama, dan diberikan sebagai bagian dari output tata letak di putaran tersebut. Pada tahap tata letak kedua, kita tidak akan menata letak sampai kehabisan ruang—sebenarnya kita tidak akan kehabisan ruang (itu sebenarnya akan menjadi error), karena kita telah diberi tempat yang sangat bagus (ya, sebaik yang tersedia) untuk menyisipkan jeda awal, agar tidak melanggar aturan jeda yang tidak perlu. Jadi, kita hanya menata letak hingga titik tersebut, lalu berhenti.
Sehubungan dengan hal itu, terkadang kita perlu melanggar beberapa permintaan penghindaran jeda, jika hal itu membantu menghindari overflow fragmentainer. Contoh:
Di sini, kita kehabisan ruang tepat sebelum #second
, tetapi memiliki "break-before:avoid". Hal ini diterjemahkan menjadi "melanggar jeda hindari", sama seperti contoh terakhir. Kita juga memiliki NGEarlyBreak dengan "melanggar orphan dan widow" (di dalam #first
> sebelum "baris 2"), yang masih belum sempurna, tetapi lebih baik daripada "melanggar break avoid". Jadi, kita akan berhenti sebelum "baris 2", yang melanggar permintaan anak yatim / janda. Spesifikasi menangani hal ini di 4.4. Unforced Breaks, yang menentukan aturan pemisahan mana yang diabaikan terlebih dahulu jika kita tidak memiliki cukup titik henti sementara untuk menghindari overflow fragmentainer.
Kesimpulan
Sasaran fungsional dari project fragmentasi blok LayoutNG adalah untuk menyediakan implementasi yang mendukung arsitektur LayoutNG dari semua yang didukung mesin lama, dan sesedikit mungkin, selain perbaikan bug. Pengecualian utamanya adalah dukungan penghindaran jeda yang lebih baik (misalnya, break-before:avoid
), karena ini adalah bagian inti dari mesin fragmentasi, sehingga harus ada di sana sejak awal, karena menambahkannya nanti akan berarti penulisan ulang lainnya.
Setelah fragmentasi blok LayoutNG selesai, kita dapat mulai menambahkan fungsi baru, seperti mendukung ukuran halaman campuran saat mencetak, kotak margin @page
saat mencetak, box-decoration-break:clone
, dan lainnya. Dan seperti LayoutNG secara umum, kami memperkirakan rasio bug dan beban pemeliharaan sistem baru akan jauh lebih rendah dari waktu ke waktu.
Ucapan terima kasih
- Una Kravets untuk "screenshot buatan tangan" yang bagus.
- Chris Harrelson untuk pemeriksaan tata bahasa, masukan, dan saran.
- Philip Jägenstedt untuk masukan dan saran.
- Rachel Andrew untuk pengeditan dan gambar contoh multi-kolom pertama.