當 CSS 區塊層級方塊 (例如段落或段落) 無法整個放入一個片段容器 (稱為 fragmentainer) 時,就會進行區塊分割,將其分割成多個片段。片段容器並非元素,而是代表多欄版面配置中的欄,或分頁式媒體中的頁面。
如要進行分割,內容必須位於分割內容區塊中。分割內容的情況最常見於多欄容器 (內容會分割為多個欄) 或列印時 (內容會分割為多個頁面)。長段落包含許多行,可能需要分割成多個片段,以便將前幾行放在第一個片段,其餘行則放在後續片段。

區塊碎裂與另一種眾所皆知的碎裂類型相似:行碎裂,也稱為「換行」。任何內嵌元素 (任何文字節點、任何 <a>
元素等) 只要包含多個字詞且允許換行,就可能會分割成多個片段。每個片段都會放入不同的行框中。行框是內嵌式分割,相當於資料欄和頁面的 fragmentainer。
LayoutNG 區塊碎裂
LayoutNGBlockFragmentation 是 LayoutNG 的重新編寫版本,最初在 Chrome 102 中推出。在資料結構方面,它將多個 NG 前資料結構替換為 NG 片段,並直接在 片段樹狀結構中顯示。
舉例來說,我們現在支援「break-before」和「break-after」CSS 屬性的「avoid」值,讓作者可避免在標題後方插入換行符號。如果網頁最後一項內容是標題,而該部分的內容則在下一頁開始,通常會顯得相當突兀。建議您在標頭前分頁。

Chrome 也支援分割溢位,因此不會將單一 (應為不可分割) 內容切割成多個欄,並正確套用陰影和轉換等繪圖效果。
LayoutNG 中的區塊碎裂現已完成
核心分割作業 (區塊容器,包括行版面配置、浮動和離開流程的定位) 已在 Chrome 102 中推出。Flex 和格線分割作業已在 Chrome 103 中推出,而表格分割作業則已在 Chrome 106 中推出。最後,列印功能已在 Chrome 108 推出。區塊區塊化是最後一個需要使用舊版引擎執行版面配置的功能。
自 Chrome 108 起,舊版引擎不再用於執行版面配置。
此外,LayoutNG 資料結構支援繪製和命中測試,但我們確實會使用一些舊版資料結構,以便讀取版面配置資訊的 JavaScript API,例如 offsetLeft
和 offsetTop
。
使用 NG 進行版面配置後,您就能實作並發布僅有 LayoutNG 實作項目 (而沒有舊版引擎對應項目) 的新功能,例如 CSS 容器查詢、錨點定位、MathML 和自訂版面配置 (Houdini)。針對容器查詢,我們提前發布了這項功能,並向開發人員發出警告,指出系統尚未支援列印功能。
我們在 2019 年推出 LayoutNG 的第一部分,其中包含一般區塊容器版面配置、內嵌版面配置、浮動和流程外定位,但不支援 Flex、格線或表格,也完全不支援區塊碎裂。我們會改用舊版版面配置引擎,用於 Flex、格線、表格,以及任何涉及區塊分割的內容。即使是分割內容中的區塊、內嵌、浮動和流程外元素,也都適用這項原則。如您所見,升級這類複雜的版面配置引擎是相當精細的作業。
此外,截至 2019 年中旬,LayoutNG 區塊區塊版面配置的大部分核心功能已實作 (在標記後方)。那麼,為什麼出貨時間這麼長?簡單來說,分割作業必須與系統的各種舊版元件正確共存,且必須先升級所有依附元件,才能移除或升級分割作業。
舊版引擎互動
舊版資料結構仍負責讀取版面配置資訊的 JavaScript API,因此我們需要以舊版引擎可理解的方式將資料寫回。這包括正確更新 LayoutMultiColumnFlowThread 等舊版多欄資料結構。
舊版引擎備用偵測和處理
當內容尚未由 LayoutNG 區塊分割處理時,我們必須改用舊版版面配置引擎。在核心 LayoutNG 區塊分割作業時,包括 flex、格線、表格和任何已列印的內容。這項作業特別棘手,因為我們需要先偵測是否需要舊版備用方案,才能在版面配置樹狀結構中建立物件。舉例來說,我們需要先偵測是否有多欄容器祖系,以及哪些 DOM 節點會成為格式設定內容,才能知道是否有多欄容器祖系。這是雞生蛋或蛋生雞的問題,沒有完美的解決方案,但只要唯一的錯誤行為是誤判 (在實際上不需要時改回舊版),就沒問題,因為版面配置行為中的任何錯誤都是 Chromium 已知的,並非新錯誤。
預先繪製樹狀檢查
預先繪製是我們在版面配置後,但在繪製前執行的作業。主要挑戰是我們仍需要檢視版面配置物件樹狀結構,但現在我們有 NG 片段,因此我們該如何處理?我們會同時檢查版面配置物件和 NG 片段樹狀結構!這項作業相當複雜,因為兩個樹狀結構之間的對應並非易事。
雖然版面配置物件樹狀結構與 DOM 樹狀結構非常相似,但片段樹狀結構是版面配置的輸出,而非輸入。除了實際反映任何分割作業的效果 (包括內嵌分割 (行片段) 和區塊分割 (欄或頁面片段)),片段樹狀結構還會在包含區塊與 DOM 子項之間建立直接的父子關係,這些子項會將該片段做為包含區塊。舉例來說,在片段樹狀結構中,由絕對定位元素產生的片段是其包含區塊片段的直接子項,即使在流程外定位子項與其包含區塊之間的祖系鏈結中,有其他節點也一樣。
如果在分割區中出現超出流程的定位元素,情況可能會更加複雜,因為超出流程的分割區會成為 fragmentainer 的直接子項 (而非 CSS 認為的包含區塊子項)。為了與舊版引擎共存,必須解決這個問題。日後,我們應該可以簡化這段程式碼,因為 LayoutNG 的設計可靈活支援所有新型版面配置模式。
舊版分割引擎的問題
舊版引擎是在網際網路早期設計的,因此不太瞭解分割的概念,即使當時技術上也存在分割問題 (為了支援列印功能)。碎片化支援功能只是在頂層 (列印) 或後端 (多欄) 加強的功能。
在版面配置可分割的內容時,舊版引擎會將所有內容版面配置為高條紋,其寬度為欄或頁面的內嵌大小,高度則視所需容納的內容而定。這個高條帶不會算入網頁,而是算入虛擬網頁,然後再重新排列以供最終顯示。概念上類似於將整篇報紙文章以一列的方式列印出來,然後再用剪刀將其剪成多個部分。(早期有些報紙確實使用類似的技術!)
舊版引擎會追蹤條帶中的虛擬頁面或欄界線。這樣一來,系統就能將不符合邊界限制的內容推送至下一頁或欄位。舉例來說,如果只有一行內容的上半部可放入引擎認為是目前頁面的內容,則會插入「分頁支柱」,將其推到引擎認為是下一頁頂端的位置。接著,大部分的實際分割作業 (「剪裁和放置」) 會在預先繪製和繪製期間的版面配置後進行,方法是將高內容條切割成多個頁面或欄位 (透過裁剪和轉譯部分)。這使得某些功能無法運作,例如在分割「之後」套用轉換和相對定位 (這是規格要求的內容)。此外,雖然舊版引擎支援部分表格分割功能,但完全不支援 Flex 或格線分割功能。
以下插圖說明在使用剪刀、放置和黏貼功能之前,三欄版面配置如何在舊版引擎中內部呈現 (我們已指定高度,因此只有四行可容納,但底部會有一些多餘空間):

由於舊版版面配置引擎在版面配置期間並未實際分割內容,因此會產生許多奇怪的副產品,例如相對定位和轉換應用方式不正確,以及在欄邊裁剪邊框陰影。
以下是使用 text-shadow 的範例:
舊版引擎無法妥善處理此問題:

您是否發現第一欄中文字陰影的部分遭到裁剪,並且改為放在第二欄頂端?這是因為舊版版面配置引擎無法瞭解分割作業。
如下所示:
接下來,我們來使用轉換和邊框陰影,讓效果更複雜一點。請注意,在舊版引擎中,裁剪和欄位溢位有誤。這是因為根據規格,轉換應以後置版面配置、後置分割效果的方式套用。使用 LayoutNG 分割時,兩者都會正常運作。這麼做可提升與 Firefox 的互通性,因為 Firefox 已提供良好的分散式支援一段時間,且大部分的測試也已通過。
舊版引擎也無法處理高大的單一內容。如果內容無法拆分為多個片段,則為單體。具有溢位捲動的元素是單一元素,因為使用者無法在非矩形區域中捲動。線框和圖片也是單一內容的其他範例。範例如下:
如果單體內容過高,無法放入欄中,舊版引擎就會粗暴地切割內容 (導致嘗試捲動可捲動容器時出現非常「有趣」的行為):
請勿讓其溢出第一個欄 (如同 LayoutNG 區塊碎裂情形):
舊版引擎支援強制暫停。舉例來說,<div style="break-before:page;">
會在 DIV 前插入分頁符號。不過,它只支援部分功能,無法找出最佳的非強制分頁符號。雖然支援 break-inside:avoid
和孤行字和寡行字,但如果透過 break-before:avoid
要求,則無法避免區塊間的斷行。以這段程式碼為例:
在此情況下,#multicol
元素可在每個資料欄中容納 5 行 (因為它的高度為 100 像素,而行高為 20 像素),因此所有 #firstchild
都能放入第一個資料欄。不過,其同胞 #secondchild
有 break-before:avoid,表示內容希望在兩者之間不出現斷裂。由於 widows
的值為 2,我們需要將 2 行 #firstchild
推送至第二個資料欄,以便處理所有避免中斷的要求。Chromium 是第一個完全支援這項功能組合的瀏覽器引擎。
NG 區塊化功能的運作方式
NG 版面配置引擎通常會透過逐層搜尋 CSS 方塊樹狀結構,來安排文件的版面配置。當節點的所有子項都已排版後,您可以產生 NGPhysicalFragment 並傳回至父項版面配置演算法,藉此完成該節點的版面配置。該演算法會將片段新增至子項片段清單,並在所有子項完成後,為自身產生片段,並在其中加入所有子項片段。這個方法會為整份文件建立片段樹狀結構。不過,這只是過於簡化的說法:舉例來說,如果元素是位於流程之外,就必須從 DOM 樹狀結構中的位置向上傳遞,才能傳遞至包含區塊,然後才能進行版面配置。為了簡化說明,我會略過這項進階細節。
除了 CSS 方塊本身,LayoutNG 也為版面配置演算法提供限制空間。這會為演算法提供資訊,例如版面配置的可用空間、是否已建立新的格式設定內容,以及先前內容的中間邊距摺疊結果。限制空間也知道片段分割器的版面配置區塊大小,以及目前區塊在其中的偏移量。這表示要換行的位址。
涉及區塊碎裂時,子項的版面配置必須在斷點處停止。分頁的原因包括頁面或欄位空間不足,或是強制分頁。接著,我們會為造訪的節點產生片段,並一路返回分割內容區塊內容根目錄 (多欄容器,或在列印時為文件根目錄)。接著,在分割內容區塊的根層級,我們會準備新的分割器,然後再次進入樹狀結構,從中斷處繼續執行。
用於在中斷後提供繼續版面配置方式的重要資料結構稱為 NGBlockBreakToken。其中包含在下一個區塊容器中正確繼續執行版面配置所需的所有資訊。NGBlockBreakToken 會與節點建立關聯,並形成 NGBlockBreakToken 樹狀結構,以便呈現每個需要暫停的節點。系統會為內部中斷的節點產生 NGPhysicalBoxFragment,並附加 NGBlockBreakToken。中斷符記會傳播至父項,形成中斷符記樹狀結構。如果我們需要在節點「前」中斷 (而非在節點內),就不會產生片段,但父節點仍需為節點建立「前」中斷符號,以便在下一個片段容器的節點樹狀結構中,到達相同位置時開始進行版面配置。
當 FragmentAiner 空間用盡 (非強制中斷) 或要求強制中斷時,系統就會插入中斷點。
規格中有一些規則可用於最佳化非強制中斷,而且在空間不足時插入中斷點不一定是正確的做法。舉例來說,有許多 CSS 屬性 (例如 break-before
) 會影響中斷位置的選擇。
在版面配置期間,為了正確實作非強制中斷規格部分,我們需要追蹤可能的適當中斷點。這個記錄表示,如果在違反中斷避免要求 (例如 break-before:avoid
或 orphans:7
) 的點上空間不足,我們可以返回並使用最後找到的最佳中斷點。每個可能的中斷點都會獲得分數,範圍從「僅在萬不得已時使用」到「中斷點的最佳位置」,中間還有一些值。如果分數為「完美」,表示在該位置插入分割線不會違反任何分割規則 (如果分數是在空間不足的點,則無需回頭尋找更理想的分割位置)。如果分數為「最後手段」,則中斷點甚至不是有效中斷點,但如果我們找不到更好的值,仍可能在該處中斷,以免 fragmentainer 溢位。
有效的暫停點通常只會出現在同層項目 (行框或區塊) 之間,而不會出現在父項與其第一個子項之間 (類別 C 暫停點除外,但我們在此處不討論這類暫停點)。在包含 break-before:avoid 的區塊同胞之前,有一個有效的暫停點,但它介於「完美」和「最後手段」之間。
在版面配置期間,我們會在名為 NGEarlyBreak 的結構體中,追蹤目前找到的最佳中斷點。提早中斷是指在區塊節點前或內部,或是在行之前 (區塊容器行或 Flex 行) 的可能中斷點。我們可能會建立 NGEarlyBreak 物件的鏈結或路徑,以防最佳中斷點位於我們先前在空間不足時略過的某個深層位置。範例如下:
在本例中,我們在 #second
前面沒有足夠的空間,但它有「break-before:avoid」,因此獲得「違反 break avoid」的斷行位置分數。在那個時間點,我們有一個 NGEarlyBreak 鏈結,其中包含「inside #outer
> inside #middle
> inside #inner
> before "line 3"」和「perfect」,因此我們寧願在那裡中斷。因此,我們需要返回並從 #outer 的開頭重新執行版面配置 (這次會略過我們找到的 NGEarlyBreak),以便在 #inner 的「第 3 行」前中斷。(我們會在「第 3 行」前中斷,讓剩餘的 4 行在下一個 fragmentainer 中結束,以便遵循 widows:4
的規則)。
這個演算法會在最適當的暫停點 (如規格中所定義) 中,依正確順序捨棄規則 (如果無法滿足所有規則)。請注意,每個分割流程最多只需重新配置一次。在第二次版面配置處理階段時,最佳中斷位置已傳遞至版面配置演算法,這是在第一個版面配置處理階段發現的中斷位置,並在該輪的版面配置輸出內容中提供。在第二次版面配置階段,我們會在空間耗盡前停止版面配置作業,事實上,我們不應耗盡空間 (這其實會導致錯誤),因為我們已提供超級精確 (至少在可用範圍內) 的位置,可插入提早中斷,以免不必要地違反任何中斷規則。所以我們只需將內容排版到該點,然後中斷。
請注意,如果違反部分避免中斷要求有助於避免 fragmentainer 溢位,我們有時就需要違反部分避免中斷要求。例如:
在本例中,#second
前方空間不足,但它有「break-before:avoid」屬性。這會轉譯為「違反 break avoid」,就像上一個範例一樣。我們也使用了 NGEarlyBreak,並「違反孤兒和寡婦」(位於 #first
內,位於「line 2」之前),雖然仍不完美,但比「違反 break avoid」好。因此,我們會在「line 2」前中斷,違反孤兒 / 寡婦要求。規格在 4.4 未強制中斷:如果沒有足夠的中斷點來避免 FragmentAiner 溢位,則會定義首先要略過哪些中斷規則。
結論
LayoutNG 區塊分割專案的功能目標,是提供 LayoutNG 架構支援的實作項目,包括舊版引擎支援的所有項目,以及除了錯誤修正之外的其他項目。主要例外狀況是,為了提供更完善的避免中斷支援 (例如 break-before:avoid
),因為這是分割引擎的核心部分,因此必須從一開始就納入,如果之後才加入,就必須重新編寫。
完成 LayoutNG 區塊碎裂問題後,我們可以開始新增功能,例如支援混合沖印頁面大小、沖印時的 @page
邊界框、box-decoration-break:clone
等等。與 LayoutNG 一樣,我們預期新系統的錯誤率和維護負擔會隨著時間大幅降低。
特別銘謝
- 感謝 Una Kravets提供精美的「手繪螢幕截圖」。
- Chris Harrelson 提供校對、意見回饋和建議。
- Philip Jägenstedt 提供意見和建議。
- Rachel Andrew 編輯內容和第一個多欄圖表範例。