用 CSS 分片打破盒子
已发表: 2022-03-10在本文中,我将向您介绍 CSS Fragmentation 规范。 您可能从未听说过它,但是,如果您曾经创建过打印样式表并想要控制页面之间的内容中断位置,或者多列布局并想要阻止列之间的图形中断,那么您已经遇到过它。
我发现人们经常用 multicol 报告的问题实际上是浏览器支持碎片的问题。 在快速了解本规范中包含的属性之后,我将解释浏览器支持的当前状态以及您可以做的一些事情,以使其在您的 multicol 和打印项目中正常工作。
什么是碎片化?
CSS 中的碎片化描述了将内容分解为不同框的过程。 目前,我们在网络上有两个地方可能会遇到碎片:当我们打印文档时,以及如果我们使用多列布局。 这两件事本质上是一样的。 当您打印(或保存为 PDF)网页时,内容会被分割成打印内容所需的页数。
当您使用 multicol 时,内容被分割成列。 每个列框就像分页上下文中的一个页面。 如果您认为一组列很像一组页面,那么它可能是一种有用的方式来思考 multicol 以及碎片如何在其中工作。
如果你看一下 CSS Fragmentation Specification,你会看到提到的第三个碎片上下文——那个上下文是 Regions。 由于当前没有可用的区域实现,因此我们不会在本文中讨论它,而是查看您在工作中可能遇到的两个上下文。
块和内联框
我将在本文中大量提及块框。 页面的每个元素都有一个框。 其中一些框被布置成块:段落、列表项、标题。 据说这些参与了块格式化上下文。 其他是内联的,例如段落中的单词、跨度和锚元素。 这些参与内联格式化上下文。 简而言之,当我提到块框时,我指的是围绕段落之类的框。 在处理碎片时,重要的是要知道您正在处理哪种盒子。
有关块和内联布局的更多信息,请参阅 MDN 文章“正常流程中的块和内联布局”。 这是我们可能在某种程度上都理解但以前可能没有遇到过的术语的事情之一。
控制休息
无论您是创建打印样式表、使用特定的打印用户代理制作 PDF 还是使用 multicol,您有时都会遇到类似这样的问题。
在下面的多列示例中,我有一些内容显示为三列。 内容的中间是一个框出的区域,它被分成两列。 我不希望这种行为——我希望盒子保持在一起。

为了解决这个问题,我将属性break-inside: avoid
添加到框中。 当元素处于碎片上下文中时, break-inside
属性控件会在元素内部中断。 在支持此属性的浏览器中,该框现在将保留在其中一列中。 列看起来不太平衡,但是,这通常比最终将 boxout 拆分为列更好。
请参阅 (Rachel Andrew.
break-inside
属性是碎片规范中详述的属性之一。 完整的属性列表如下:
-
break-before
-
break-after
-
break-inside
-
orphans
-
widows
-
box-decoration-break
在我们进入浏览器中实际发生的事情之前,让我们看看这些应该如何工作。
break-before
和break-after
属性
有两个属性可以控制块级框之间的中断: break-before
和break-after
。 如果你有一个h2
后跟两个段落<p>
你有三个块框,你可以使用这些属性来控制标题和第一段之间或两个段落之间的中断。
这些属性用于选择器,这些选择器针对要在之前或之后中断的元素。
例如,您可能希望每次出现第 2 级标题时,您的打印样式表都会跳到一个新页面。 在这种情况下,您将在h2
元素上使用break-before: page
。 这控制了碎片并确保在h2
元素的框之前总是有一个中断。
h2 { break-before: page; }
另一个常见的要求是防止标题成为页面或列上的最后一件事。 在这种情况下,您可以使用值为avoid
break-after
。 这应该防止在元素框之后直接中断:
h1, h2, h3, h4 { break-after: avoid; }
片段中的片段
您可能有一个元素碎片嵌套在另一个元素中。 例如,在被分页的东西里面有一个多列。 在这种情况下,您可能希望控制页面而不是列的中断,或者相反。 这就是为什么我们有诸如page
之类的值,它总是在元素之前或之后强制中断,但仅当片段是页面时。 或者avoid-page
,这将避免元素之前或之后的中断,仅用于分页上下文。
这同样适用于列。 如果您使用 value column
,这将始终在该元素之前或之后强制中断,但仅适用于多列上下文。 值avoid-column
将防止在多列上下文中中断。
第 4 级规范中有一个always
值,表示您想要突破所有内容——页面或列。 但是,作为规范的最新补充,它目前对我们没有用处。
分页媒体的附加值
如果您正在创建一本书或杂志,则您有左页和右页。 您可能想要控制中断,以便将某些内容强制到跨页的左侧或右侧页面上。 因此,使用以下命令会在h2
之前插入一到两页分页符,以确保它被格式化为正确的页面。
h2 { break-before: right; }
还有与页面进展相关的正向和反向值,因为以垂直或从右到左语言编写的书籍与用英语编写的书籍具有不同的页面进展。 我不会在本文中进一步讨论这些值,因为我主要关心的是这次浏览器可能提供的功能。
break-inside
我们已经看到了break-inside
属性的一个例子。 此属性控制块框内的中断,例如在段落、标题或 div内。
您可能不想破坏的内容可能包括如上所述的框出:您不希望标题与图像、表格、列表等分离的图形。 添加break-inside: avoid
在任何碎片上下文中您不希望破坏的任何容器。 如果您只想避免列之间的中断,请使用break-inside: avoid-column
和页之间break-inside: avoid-page
。
orphans
widows
属性
orphans
和widows
属性处理在中断之前或之后应该留下多少行(由列或新页面引起)。 例如,如果我想避免在列的末尾留下一行,我会使用orphans
属性,就像在排版中一样,孤儿是单独出现在页面底部的段落的第一行该段的其余部分被分到另一页。 该属性应该添加到正在分段的相同元素(在我们的例子中,multicol 容器)。
.container { column-count: 3; orphans: 2; }
要控制中断后列或页面顶部应有多少行,请使用widows
:
.container { column-count: 3; widows: 2; }
这些属性处理行内框之间的中断,例如段落中的单词行。 因此,它们在标题或其他块元素单独位于列或页面底部的情况下没有帮助,您需要上面讨论的中断属性。
盒子装饰
最后一个可能感兴趣的属性是box-decoration-break
属性。 这控制了您在两个列框或页面之间有一个边框断开的框的情况。 你想让边界基本上被切成两半吗? 还是您希望盒子的两半中的每一半都完全包裹在边框中?
第一种情况是默认情况,就像您将box-decoration-break
属性设置为在盒子上slice
一样。
.box { box-decoration-break: slice; }

要获得第二种行为,请将box-decoration-break
设置为克隆。
.box { box-decoration-break: clone; }

浏览器支持分片
现在我们来看看我没有上面的一堆 CodePen 示例来向您演示所有这些的原因,以及我写这篇文章的主要原因。 浏览器对这些属性的支持不是很好。
如果您在 Paged Media 中使用特定的用户代理(例如 Prince)工作,那么您可以享受对碎片的非常好的支持,并且可能会发现这些属性非常有用。 如果您正在使用 web 浏览器,无论是在 multicol 中,创建打印样式表,还是使用 Headless Chrome 之类的东西来生成 PDF,支持有点不完整。 你会发现支持最好的浏览器是 Edge——直到它迁移到 Chromium!

由于将碎片属性与 multicol 混合在一起,然后为遗留属性提供了一些单独的数据,我可以使用它对解释支持并没有太大帮助。 因此,作为我为 MDN 记录属性及其支持所做的工作的一部分,我开始测试实际的浏览器支持。 以下是基于该测试的一些建议。
旧版和供应商前缀属性
没有历史课我不能走得更远。 如果您发现您确实需要对碎片的支持,那么您可能会在最初是 CSS2 的一部分的遗留属性中找到一些缓解(或在某些存在的前缀属性中)。
在 CSS2 中,有一些属性可以控制分页。 Multicol 那时还不存在,所以唯一的碎片化上下文是分页的。 这意味着引入了三个特定的分页属性:
-
page-break-before
-
page-break-after
-
page-break-inside
它们的工作方式类似于没有page-
前缀的更通用的属性,控制框之前、之后和内部的中断。 对于打印样式表,您会发现一些不支持新的break-
属性的旧浏览器确实支持这些页面前缀属性。 这些属性被视为新属性的别名。
在 2005 年的 multicol 规范工作草案中详细介绍了 multicol 的破坏属性——使用以column-
为前缀的属性(即column-break-before
、 column-break-after
和column-break-inside
)。 到 2009 年,这些都消失了,multicol 规范中出现了一个关于无前缀中断属性的草案,最终进入 CSS Fragmentation 规范。
但是,一些供应商前缀列特定的属性是基于这些属性实现的。 这些是:
-
-webkit-column-break-before
-
-webkit-column-break-after
-
-webkit-column-break-inside
支持 Multicol 中的分片
以下内容基于在多列上下文中测试这些特性。 我试图解释什么是可能的,但请在您可用的任何浏览器中查看 CodePens。
Multicol 和break-inside
multicol 中的支持最适合break-inside
属性。 Chrome、Firefox、Edge 和 Safari 的最新版本都支持break-inside: avoid
. 所以你应该发现在使用 multicol 时可以防止框在列之间断开。
几个浏览器,除了 Firefox,支持-webkit-column-break-inside
属性,这可以与avoid
值一起使用,并且可以防止框在不支持break-inside
的列之间断开。
Firefox 支持page-break-inside: avoid
在多列中。 因此,使用此属性将防止 Firefox 65 之前的 Firefox 浏览器中的框内中断。
这意味着,如果您想防止 multicol 中的框之间出现中断,使用以下 CSS 将覆盖尽可能多的浏览器,并尽可能回溯。
.box { -webkit-column-break-inside: avoid; page-break-inside: avoid; break-inside: avoid; }
至于column
值,明确指出您只想避免列之间而不是页面之间的中断,适用于除 Firefox 之外的所有浏览器。
下面的 CodePen 在 multicol 中汇总了其中一些测试,因此您可以自己尝试它们。
请参阅 Pen Multicol Fragmentation Test:Rachel Andrew 的break-inside。
Multicol 和break-before
为了防止在元素之前中断,您应该能够使用break-before: avoid
或break-before: avoid-column
。 避免属性没有浏览器支持。
Edge 支持break-before: column
总是在元素的框之前强制中断。
Safari、Chrome 和 Edge 也支持-webkit-column-break-before: always
,这将在元素的框之前强制中断。 因此,如果你想在元素的框之前强制中断,你应该使用:
.box { -webkit-column-break-before: always; break-before: column; }
防止在盒子前休息目前是一项不可能完成的任务。 您可以在下面使用这些属性的一些示例:
请参阅 Pen Multicol Fragmentation Test:Rachel Andrew 的 break-before)。
Multicol 和break-after
为了防止在一个元素之后出现中断,为了避免它成为列底部的最后一件事,您应该能够使用break-after: avoid
和break-after: avoid-column
。 唯一支持这些的浏览器是 Edge。
Edge 还支持通过使用break-after: column
强制在元素后中断,Chrome 支持break-after: column
和-webkit-column-break-after: always
。
Firefox 不支持break-after
或任何前缀属性来强制或允许在框后中断。
因此,除了 Edge 之外,您无法真正避免一个盒子后的中断。 如果你想强制它们,你会在某些浏览器中使用以下 CSS 获得结果:
.box { -webkit-break-after: always; break-after: column; }
请参阅 Pen Multicol Fragmentation Test:Rachel Andrew 的 break-after)。
从浏览器打印时的支持
无论您是直接从桌面浏览器打印,还是使用无头 Chrome 或其他依赖浏览器技术的解决方案生成 PDF 文件,都没有任何区别。 您依赖于浏览器对碎片属性的支持。
如果您创建打印样式表,您会发现对 break 属性的支持与 multicol 类似; 但是,为了支持旧版浏览器,您应该将属性加倍以使用以page-
为前缀的属性。
打印样式表和break-inside
在现代浏览器中, break-inside
属性可用于防止框内中断,添加page-break-inside
属性以添加对旧浏览器的支持。
.box { page-break-inside: avoid; break-inside: avoid; }
打印样式表和break-before
要在框之前强制中断,请使用break-before:page
和page-break-before: always
。
.box { page-break-before: always; break-before: page; }
为了避免在盒子之前出现中断,请使用break-before: avoid-page
和page-break-before: avoid
。
.box { page-break-before: avoid; break-before: avoid-page; }
与我们看到的等效多列值相比,对page
和avoid-page
值的支持更好。 大多数现代浏览器都支持。
打印样式表和break-before
要在框后强制中断,请使用break-after: page
和page-break-after: always
。
.box { page-break-after: always; break-after: page; }
为了防止在一个盒子之后出现中断,请使用break-after: avoid-page
和page-break-after: avoid
。
.box { page-break-after: avoid; break-after: avoid-page; }
寡妇和孤儿
widows
和orphans
属性享有良好的跨浏览器支持——唯一没有实现的浏览器是 Firefox。 我建议在创建多列布局或打印样式表时使用这些。 如果他们因为某种原因不工作,你会得到寡妇和孤儿,这并不理想,但也不是灾难。 如果它们确实有效,那么您的排版看起来会更好。
盒子装饰打破
box-decoration-break
的最后一个属性在 Firefox 中支持 multicol 和 print。 Safari、Chrome 和其他基于 Chromium 的浏览器支持-webkit-box-decoration-break
,但仅限于内联元素。 因此,例如,您可以克隆句子的边界线; 在我们正在研究的情况下,他们没有得到支持。
在下面的 CodePen 中,您可以看到测试-webkit-box-decoration-break: clone
with Feature Queries 返回 true; 但是,该属性对 multicol 上下文中框的边框没有影响。
请参阅 Rachel Andrew 的 Pen Multicol:box-decoration-break。
使用分片
如您所见,目前浏览器的碎片化状态有些碎片化! 也就是说,你可以达到一个合理的数量,如果它失败了,结果往往不是最理想的,但不是灾难。 这意味着值得一试。
值得注意的是,对这些属性过于沉重可能会导致与您希望的结果不同。 如果您在 Web 上工作,而不是在每个段落之后打印和强制换行,那么最终的段落比列的空间多,multicol 最终会在内联方向溢出。 它将用完列来放置您的其他段落。 因此,即使有支持,您仍然需要仔细测试,并记住在很多情况下少即是多。
更多资源
要阅读有关 MDN 属性的更多信息,我最近更新了那里的页面,并且还试图使浏览器兼容数据保持最新。 CSS Fragmentation 的主页链接到各个属性页面,其中包含更多示例、浏览器兼容性数据和有关使用这些属性的其他信息。