在 CSS display 中使用多关键字语法

CSS display 模块为 CSS display 属性定义了一种多关键字语法。本指南将解释这种多关键字语法。

注意:多关键字语法也称为“双值语法”或“多值语法”。

当我们改变 display 属性的值时会发生什么?

我们学习 CSS 时最先了解的事情之一就是,有些元素是块级元素,有些是行内元素。这些是它们的外部显示类型。例如,<h1><p> 默认是块级的,而 <span> 是行内的。使用 display 属性,我们可以在块级和行内之间切换。例如,要使标题变为行内,我们会使用以下 CSS:

css
h1 {
  display: inline;
}

display 属性还允许我们在设置 display: griddisplay: flex 时使用 CSS 网格布局Flexbox。需要理解的重要概念是,改变一个元素的 display 值可以改变其直接子元素的格式化上下文。当你使用 display: flexdisplay: grid 时,该元素的子元素会变成伸缩项或网格项,并响应网格和 Flexbox 规范中的属性。

然而,网格和 Flexbox 展示的是,一个元素同时具有外部内部显示类型。外部显示类型描述了该元素是块级还是行内级。内部显示类型则描述了该盒子内子元素的行为方式。

举个例子,当我们使用 display: flex 时,我们创建了一个块级容器,其子元素是伸缩项。这些子元素被描述为参与伸缩格式化上下文。如果你拿一个 <span>——通常是行内级元素——并对其应用 display: flex,你就可以看到这一点。这个 <span> 变成了一个块级元素。它在布局中与其他盒子的关系表现得就像块级元素一样。这就像你对 span 应用了 display: block,但同时我们也得到了子元素行为的改变。

下面的实时示例中有一个应用了 display: flex<span>。它变成了一个块级盒子,占据了行内方向上所有可用的空间。你现在可以使用 justify-content: space-between 来将这些空间分配到两个伸缩项之间。

html
<span class="flex"> Some text <em>emphasized text</em> </span>
css
body {
  font: 1.2em / 1.5 sans-serif;
}
.flex {
  border: 5px solid #cccccc;
  display: flex;
  justify-content: space-between;
}

创建行内伸缩容器也是可以的。如果你使用单值 inline-flex,你将得到一个行内级盒子,其子元素是伸缩项。这些子元素的行为方式与块级容器的伸缩子元素相同。唯一改变的是父元素现在是一个行内级盒子。因此,它的行为就像其他行内级元素一样,不会像块级盒子那样占据整个宽度(或行内维度的尺寸)。这意味着一些后续的文本可以出现在伸缩容器的旁边。

html
<div class="flex">
  <div>One</div>
  <div>Two</div>
</div>
Text following the flex container.
css
body {
  font: 1.2em / 1.5 sans-serif;
}
.flex > div {
  border: 2px solid rgb(96 139 168);
  border-radius: 5px;
  background-color: rgb(96 139 168 / 0.2);
}

.flex {
  border: 5px solid #cccccc;
  display: inline-flex;
}

在使用网格布局时也是如此。使用 display: grid 会得到一个块级盒子,它为其直接子元素创建一个网格格式化上下文。使用 display: inline-grid 会创建一个行内级盒子,它为其子元素创建一个网格格式化上下文。

使用多关键字语法

从上面的解释可以看出,display 属性具有相当大的能力。它不仅指示了某个元素在页面上与其他盒子的关系是块级还是行内级,还指示了它所应用的盒子内部的格式化上下文。为了更好地描述这种行为,display 属性允许设置两个值——一个外部值和一个内部值。原来的单值语法也仍然有效。

这意味着,我们不再使用 display: flex 来创建带有伸缩子元素的块级盒子,而是使用 display: block flex。不再使用 display: inline-flex 来创建带有伸缩子元素的行内级盒子,而是使用 display: inline flex。下面的例子演示了这些值。

html
<h1>Multiple values for display</h1>

<div class="flex flex1">
  <div>Item One</div>
  <div>Item Two</div>
  <div>Item Three</div>
</div>

<p>The first example is a block element with flex children.</p>

<div class="flex flex2">
  <div>Item One</div>
  <div>Item Two</div>
  <div>Item Three</div>
</div>
The second example is an inline element with flex children.
css
body {
  font: 1.2em / 1.5 sans-serif;
}
.flex {
  border: 5px solid #cccccc;
  gap: 10px;
}

.flex > * {
  border: 2px solid rgb(96 139 168);
  border-radius: 5px;
  background-color: rgb(96 139 168 / 0.2);
}

.flex1 {
  display: block flex;
}

.flex2 {
  display: inline flex;
}

所有现有的 display 值都有对应的映射关系;下表列出了最常见的几种。要查看完整列表,请参阅 display 属性规范中的表格。

单个值 多值
block block flow
flow-root block flow-root
inline inline flow
inline-block inline flow-root
flex block flex
inline-flex inline flex
grid block grid
inline-grid inline grid

display: block flow-root 和 display: inline flow-root

关于这种多值语法如何帮助澄清 CSS 布局,我们可以看看上表中一些你可能不太熟悉的值。多关键字 display: block flow-root 映射到一个单值:display: flow-root。这个值的唯一目的就是创建一个新的块格式化上下文(BFC)。BFC 确保你盒子里的所有东西都留在里面,而盒子外的东西不能侵入进来。

在下面的示例中,两个 <p> 元素,其中一个在 <div> 内,演示了 display 值如何影响格式化上下文。为了让我们能专注于后面的元素,第一个带有演示控件的 <div> 元素被隐藏了。我们应该关注的元素是“parent”、“child”和“sibling”这几个 <div><p> 元素,你可以通过它们的 ID 来区分。

这个布局值得注意的是,父元素和子元素之间没有内容,并且子元素应用了上外边距。你可能期望上外边距能有效地将子元素在父元素内部向下推,但实际发生的是一种叫做外边距折叠的现象。在这种情况下,子元素的外边距延伸到了父元素边界框的上方,并将父元素进一步向下推。如果你在浏览器的开发者工具中检查子元素的盒模型,会更容易看到这一点。

更改 <select> 元素中选定的选项,以查看不同 display 值的效果。你可以使用任何带有 flow-root 的值来为父元素创建一个新的格式化上下文,使得子元素的外边距相对于其父元素的外边缘,从而避免外边距折叠。在 display: flow-rootdisplay: block flow-root 之间切换将达到与单值 flow-root 关键字相同的效果。

css
div,
p {
  outline: 2px solid black;
  background-color: cornflowerblue;
  display: block;
  margin-bottom: 2rem;
}

#parent {
  background-color: oldlace;
  min-height: 2rem;
}

#child {
  margin-top: 4rem;
  outline: 2px dashed red;
}

#sibling {
  background-color: lavender;
}
html
<div id="parent">
  <p id="child">The #child paragraph (nested in #parent).</p>
</div>
<p id="sibling">The #sibling paragraph (sibling of #parent).</p>

如果你考虑到块级和行内布局(有时称为常规流),flow-root 这个值就说得通了。我们的 HTML 页面会创建一个新的格式化上下文(浮动和外边距不能超出边界),并且我们的内容会以常规流的方式进行布局,使用块级和行内布局,除非我们改变 display 的值来使用其他格式化上下文。创建一个网格或伸缩容器也会创建一个新的格式化上下文(分别是网格或伸缩格式化上下文)。它们也能包含内部的所有东西。然而,如果你想包含浮动和外边距,但继续使用块级和行内布局,你可以创建一个新的流根(flow root),并重新开始块级和行内布局。从那一点向下,所有东西都被包含在新的流根内。

这就是为什么 display: flow-root 可以用多关键字语法 display: block flow-root 来书写。你正在创建一个块格式化上下文,它有一个块级盒子,其子元素参与常规流。那么与之配对的 display: inline flow-root 呢?这是目前描述 display: inline-block 的方式。

display: inline-block 这个值自 CSS 早期就存在了。我们倾向于使用它,是为了让内边距(padding)能将行内项推离一个元素,例如在创建导航项时,或者像下面的例子中那样,想为一个行内元素添加带内边距的背景。

html
<p>
  This paragraph has a span <span class="inline-block">with padding</span> it is
  an inline-block so the padding is contained and pushes the other line boxes
  away.
</p>
css
body {
  font: 1.2em / 1.5 sans-serif;
}
p {
  border: 2px dashed;
  width: 300px;
}
.inline-block {
  background-color: rgb(0 0 0 / 0.4);
  color: white;
  padding: 10px;
  display: inline-block;
}

然而,一个带有 display: inline-block 的元素也会包含浮动。它包含了这个行内级盒子内部的所有东西。因此,display: inline-block 的作用与 display: flow-root 完全相同,只是它是一个行内级盒子,而不是块级盒子。双值语法准确地描述了该值所发生的情况。在上面的例子中,你可以将 display: inline-block 改为 display: inline flow-root,并得到相同的结果。

display 的旧值怎么办?

display 的单值在规范中被描述为遗留值,目前使用多关键字版本并没有任何好处,因为每个多关键字版本都有一个直接映射到遗留版本,如上表所示。

为了处理 display 的单值,规范解释了如果只使用外部值 blockinline 该怎么办:

“如果指定了 <display-outside> 值但省略了 <display-inside>,则元素的内部显示类型默认为 flow。”

这意味着其行为与单值世界中完全一样。如果你指定 display: blockdisplay: inline,那会改变盒子的外部显示值,但任何子元素都会继续在常规流中。如果只指定了内部值 flexgridflow-root,那么规范解释说外部值应设置为 block

“如果指定了 <display-inside> 值但省略了 <display-outside>,则元素的外部显示类型默认为 block——除了 ruby,它默认为 inline。”

最后,我们有一些遗留的预组合行内级值

  • inline-block
  • inline-table
  • inline-flex
  • inline-grid

如果支持的浏览器遇到这些单值,它会像处理多关键字版本一样处理它们:

  • inline flow-root
  • inline table
  • inline flex
  • inline grid

所以所有当前的情况都得到了妥善处理,这意味着我们既能保持使用单值的现有和新网站的兼容性,又允许规范继续发展。