掌握弹性项目的换行

Flexbox 最初被设计为单维布局工具——它处理将项目按行或列进行布局——但不能同时处理两者。然而,将 flex 项目换行是可能的,如果 flex-directionrow,则创建新行;如果 flex-directioncolumn,则创建新列。本指南解释了 flexbox 的换行、其设计用途,以及哪些情况需要使用 CSS 网格布局而不是 flexbox。

实现换行

flex-wrap 属性的初始值是 nowrap。这意味着如果一组 flex 项目对于其 flex 容器来说太宽,它们将会溢出。要让它们在过宽时换行,可以添加值为 wrapflex-wrap 属性,或者使用简写属性 flex-flow 并设置值为 row wrapcolumn wrap。这样,当项目溢出其容器时,它们就会换到新的一行或一列。

在此示例中,有十个 flex 项目,它们的 flex-basis160px,并且可以放大和缩小。一旦一行中没有足够的空间再放置一个 160 像素的项目,就会创建一条新的 flex 行。根据需要创建新行,直到所有项目都被放置好。由于项目可以放大,它们会伸展以完全填满每一行。如果最后一行只有一个项目,它将伸展以填满整行。

html
<div class="box">
  <div>One</div>
  <div>Two</div>
  <div>Three</div>
  <div>Four</div>
  <div>Five</div>
  <div>Six</div>
  <div>Seven</div>
  <div>Eight</div>
  <div>Nine</div>
  <div>Ten</div>
</div>
css
.box {
  width: 500px;
  border: 2px dotted rgb(96 139 168);
  display: flex;
  flex-wrap: wrap;
}

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

flex 列的情况也是如此。要换行并创建新列,容器需要有一个高度。在列的情况下,项目会垂直拉伸以完全填满每一列。

html
<div class="box">
  <div>One</div>
  <div>Two</div>
  <div>Three</div>
  <div>Four</div>
  <div>Five</div>
  <div>Six</div>
  <div>Seven</div>
  <div>Eight</div>
  <div>Nine</div>
  <div>Ten</div>
</div>
css
.box {
  border: 2px dotted rgb(96 139 168);
  height: 300px;
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
}
.box > * {
  border: 2px solid rgb(96 139 168);
  border-radius: 5px;
  background-color: rgb(96 139 168 / 0.2);
  flex: 1 1 80px;
}

换行与 flex-direction

当与 flex-direction 结合使用时,换行的行为符合预期。如果 flex-direction 设置为 row-reverse,那么项目将从容器的末端边缘开始,并以反向顺序排列成行。

html
<div class="box">
  <div>One</div>
  <div>Two</div>
  <div>Three</div>
  <div>Four</div>
  <div>Five</div>
  <div>Six</div>
  <div>Seven</div>
  <div>Eight</div>
  <div>Nine</div>
  <div>Ten</div>
</div>
css
.box {
  border: 2px dotted rgb(96 139 168);
  display: flex;
  flex-wrap: wrap;
  flex-direction: row-reverse;
  width: 500px;
}
.box > * {
  border: 2px solid rgb(96 139 168);
  border-radius: 5px;
  background-color: rgb(96 139 168 / 0.2);
  flex: 1 1 160px;
}

请注意,反向只发生在行内(主轴)方向上。我们从右边开始,然后换到第二行,再次从右边开始。我们不是在两个方向上都进行反向,即不是从容器底部向上排列!

单维布局解析

正如我们从上面的例子中看到的,如果我们的项目被允许放大和缩小,当最后一行或一列的项目较少时,这些项目会放大以填补可用空间。

flexbox 没有提供任何功能来让一行中的项目与上一行的项目对齐——每条 flex 行都像一个新的 flex 容器。它处理的是主轴上的空间分配。如果只有一个项目,并且该项目被允许放大,它将填满整个主轴,就像你只有一个项目的 flex 容器一样。如果你想要二维布局,那么你可能需要的是网格布局。

这个例子演示了其中的区别,它使用 CSS 网格布局来创建一个布局,该布局包含尽可能多的至少 160px 宽的列,并将多余的空间分配给所有列。我们使用了与上面flexbox 换行示例相同的 HTML,但对其设置了 display: grid。我们没有使用在 flexbox 之外无效的 flex 简写属性,而是通过 grid-template-columns 直接在容器上设置项目的最小宽度和放大能力。使用 CSS 网格,最后一个项目会停留在其网格单元格中;当最后一行项目较少时,网格项目不会拉伸。

html
<div class="box">
  <div>One</div>
  <div>Two</div>
  <div>Three</div>
  <div>Four</div>
  <div>Five</div>
  <div>Six</div>
  <div>Seven</div>
  <div>Eight</div>
  <div>Nine</div>
  <div>Ten</div>
</div>
css
.box {
  border: 2px dotted rgb(96 139 168);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  width: 500px;
}

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

这就是一维布局和二维布局的区别。在像 flexbox 这样的一维布局方法中,我们只控制行或列。在二维网格布局中,我们同时控制两者。如果你想逐行进行空间分配,请使用 Flexbox。如果不想,请使用 CSS 网格。

基于 flexbox 的网格系统是如何工作的?

基于 flexbox 的布局可以被强制对齐成像网格系统一样,但这并非 flexbox 的设计初衷。如果你给 flex 项目分配了百分比宽度——无论是使用 flex-basis 还是直接为项目添加宽度并让 flex-basis 的值为 auto——你可以营造出二维布局的错觉。

在此示例中,flex-growflex-shrink 都被设置为 0,以使 flex 项目变为不可伸缩。其灵活性通过百分比来控制。

html
<div class="box">
  <div>One</div>
  <div>Two</div>
  <div>Three</div>
  <div>Four</div>
  <div>Five</div>
  <div>Six</div>
  <div>Seven</div>
  <div>Eight</div>
  <div>Nine</div>
  <div>Ten</div>
</div>
css
* {
  box-sizing: border-box;
}

.box {
  width: 500px;
  border: 2px dotted rgb(96 139 168);
  display: flex;
  flex-wrap: wrap;
}

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

这种技术可以让你在交叉轴上对齐 flex 项目。然而,当你发现自己正在以这种方式为 flex 项目添加宽度,或者添加空的 flex 项目来占据空间时,这通常是一个很好的迹象,表明你可能需要为该组件切换到 CSS 网格布局。

在项目之间创建间距

要在 flex 项目之间创建间隙或间距,可以直接在 flex 容器上使用 gap 属性,在相邻的 flex 项目之间创建固定的空间。gap 属性是 row-gapcolumn-gap 的简写。这些属性指定了网格、flex 和多列布局中行与列之间的间距大小。

gap 属性并不是唯一可以在项目之间增加空间的属性。外边距(margin)、内边距(padding)、justify-contentalign-content 也可以增加间距的大小,从而影响间距的实际尺寸。

要了解 gap 属性与 margin 在两个轴上的区别,请尝试在下面的样式表中更改容器 .boxgap 值,并为 .box > * 规则添加一个 margin 值。点击“Reset”按钮可以恢复到之前的值。

html
<div class="wrapper">
  <div class="box">
    <div>One</div>
    <div>Two</div>
    <div>Three</div>
    <div>Four</div>
    <div>Five</div>
    <div>Six</div>
    <div>Seven</div>
    <div>Eight</div>
    <div>Nine</div>
    <div>Ten</div>
  </div>
</div>
css
.wrapper {
  border: 2px dotted rgb(96 139 168);
  width: 500px;
}
.box {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.box > * {
  flex: 1 1 160px;
  border: 2px solid rgb(96 139 168);
  border-radius: 5px;
  background-color: rgb(96 139 168 / 0.2);
}

折叠的项目

flexbox 规范详细说明了如果一个 flex 项目通过设置 visibility: collapse 而被折叠时应该发生什么。请参阅 MDN 关于 visibility 属性的文档。规范对该行为的描述如下:

“在 flex 项目上指定 visibility: collapse 会使其成为一个*折叠的 flex 项目*,产生类似于在表格行或表格列上使用 visibility: collapse 的效果:折叠的 flex 项目会从渲染中完全移除,但会留下一个‘支柱’(strut),以保持 flex 行的交叉轴尺寸稳定。因此,如果一个 flex 容器只有一条 flex 行,动态地折叠或展开项目可能会改变 flex 容器的主轴尺寸,但保证不会影响其交叉轴尺寸,也不会导致页面其余部分的布局‘晃动’。然而,折叠后会重新进行 flex 行的换行,因此具有多行的 flex 容器的交叉轴尺寸可能会也可能不会改变。” - 折叠的项目

如果你想用 JavaScript 来控制 flex 项目以显示和隐藏内容,这种行为会很有用。规范中的例子就演示了这样一种模式。

在下面的实时示例中,不换行的 flex 容器包含一行三个 flex 项目,它们被设置为等尺寸伸缩。第三个项目有多行内容,使得容器变高。align-items 的默认值是 normal;对于 flex 项目,normal 的行为与 stretch 相同,所以所有项目默认都会拉伸,以填充容器的交叉轴高度。

创建交叉轴尺寸的项目被设置为 visibility: collapse,这会根据浏览器的不同,折叠或隐藏该 flex 项目。无论哪种情况,flex 容器都会保留一个交叉轴尺寸的*支柱*,即使它不可见。这样,如果该项目变为可见,单行 flex 容器的交叉轴尺寸将不会改变。如果你从 CSS 中移除 visibility: collapse 或将值更改为 visible,你会看到项目出现,主轴空间在未折叠的项目之间重新分配,而交叉轴尺寸保持不变。

注意:下面的示例请使用 Firefox,因为其他主流浏览器会将 collapse 处理为 hidden

html
<div class="box">
  <div>One</div>
  <div>Two</div>
  <div class="collapse">Three <br />has <br />extra <br />text</div>
</div>
css
.box {
  border: 2px dotted rgb(96 139 168);
  display: flex;
  width: 600px;
}
.box > * {
  flex: 1 1 200px;
  border: 2px solid rgb(96 139 168);
  border-radius: 5px;
  background-color: rgb(96 139 168 / 0.2);
}
.collapse {
  visibility: collapse;
}

以上是一个单行、不换行的 flex 容器,其固定尺寸为 600px,因此无论项目是可见还是折叠,宽度都相同。重要的是要理解,虽然容器保留了折叠项目交叉轴尺寸的支柱,但主轴尺寸并未保留。多行 flex 容器在从渲染中移除折叠项目后会重新进行换行。折叠项目在主轴方向上留下的新空间可能会导致未折叠的项目被放置在与未折叠时不同的行上。因为每一行都像一个独立的单行 flex 容器一样进行布局,其构成在折叠后可能会改变,所以它的交叉轴尺寸也可能改变。

下面的例子展示了这种行为。第三个 flex 项目被折叠,因此它在主轴上不占用任何空间(行内尺寸为 0)。折叠时,它的支柱位于第一行第四个项目之后,第一行的高度足以容纳第三个项目本应有的三行文本。然后,如果你取消折叠该项目(例如,通过移除 collapse 类),第一行将不再有足够的水平空间容纳第五个项目,它会移动到第二行。这导致第二行变高以适应其新成员的两行文本,而最后一个 flex 项目被推到了新的一行。由于第二行变高和新增了第三行,flex 容器比之前高了很多。

注意:下面的示例请使用 Firefox,因为其他主流浏览器会将 collapse 处理为 hidden

html
<div class="box">
  <div>One</div>
  <div>Two is the width of this sentence.</div>
  <div class="collapse">Three <br />is <br />five <br />lines <br />tall.</div>
  <div>Four</div>
  <div>Five<br />Five</div>
  <div>Six</div>
  <div>Seven</div>
  <div>Eight</div>
  <div>Nine</div>
  <div>Ten</div>
  <div>Eleven is longer</div>
</div>
css
.box {
  border: 2px dotted rgb(96 139 168);
  width: 500px;
  display: flex;
  flex-wrap: wrap;
}
.box > * {
  padding: 10px;
  border: 2px solid rgb(96 139 168);
  border-radius: 5px;
  background-color: rgb(96 139 168 / 0.2);
  flex: 1 1 auto;
  min-width: 50px;
}
.collapse {
  visibility: collapse;
}

如果这给你的布局带来了问题,可能需要重新考虑结构,例如,将每一行放入一个单独的 flex 容器中,这样它们就不会换行了。

使用 visibility: hiddendisplay: none

在之前的实时示例中,尝试用 visibility: hiddendisplay: none 替代 visibility: collapse。使用 visibility: hidden,项目会变得不可见,但其盒子仍在格式化结构中保留,因此它的行为仍然像是布局的一部分。当你使用 display: none 时,该项目会从格式化结构中完全移除。它不仅不可见,其结构也被移除了。这意味着计数器会忽略它,像过渡(transition)之类的效果也不会运行。