图解CSS: Grid布局(Part7)

发布于 大漠

网格项目自动放置:grid-auto-flow

在 CSS 网格布局中,在网格项目上使用 grid-rowgrid-columngrid-area 等属性明确地把网格项目放置在网格上。除此之外,CSS 网格布局规范还包含另外一组规则,用来约定未被明确指定位置的网格项目该如何放置。你会发现针对含有数个项目的网格,实际上最简单的方式就是使用自动放置。如果没有为项目指定位置信息,它们就会把自己摆放在网格中,每个单元格中放一个。

对于 自动放置(Auto Placement) 概念在规范中也有明确的定义

Grid items that aren’t explicitly placed are automatically placed into an unoccupied space in the grid container.

大致意思是 没有明确放置的网格项目会被自动放置到网格容器中一个未被占用的空间(网格单元格)

为了说明“自动放置”是如何工作的,我们从一个简单的例子着手。

.container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}

示例中,我们使用 grid-template-columns: repeat(3, 1fr) 构建了一个 3 x 2 显式的网格(三列两行)。这个示例可以说自动放置的规则是相当的直观。网格项目沿着行(或内联)轴放置,直到它们填满该行,然后它们将被包裹到下一行(如果没有定义一个新的行,则创建一个新的行)。

再来看隐式的网格,比如下面这样的示例:

.container {
    display: grid;
    gap: 10px;
}

.grid__item:nth-child(1) {
    grid-row: 1;
    grid-column: 1 / span 2;
}

.grid__item:nth-child(2) {
    grid-row: 2;
    grid-column: 2
}

上面示例使用 grid-rowgrid-column 构建了一个 2 x 3 (两列三行)的网格。第一个网格项目放置在第一行的两列,第二个网格项目放置在第二行第二列。这就在第二行第一列留下了一个空的网格单元格,即第三个网格项目被放置的地方,且该网格项目自动放置到第二行第一列的网格单元格上。剩下的第四个,第五个网格项目自动放置在第三行的第一列和第二列网格单元格上:

当然,你也可做更复杂的事情,比如在一个维度(行或列网格轨道)中设置一个指定的位置,而把另一个维度留作自动,为网格项目申请一个以上的网格单元格(使用span 合并网格单元格),等等。比如:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}
.grid__item:nth-child(1) {
    grid-column: span 2;
}

.grid__item:nth-child(2) {
    grid-column: 3;
}

.grid__item:nth-child(3) {
    grid-row: span 2;
}

.grid__item:nth-child(4) {
    grid-row: span 2;
    grid-column: span 2;
}

它的运行大致像下图这样:

简单的分析一下:

  • 网格项目①:这是第一个放置的网格项目,这个网格项目需要2列,它被放置在网格的第1行第1~2列(grid-column: span 2;),因为它们是空的
  • 网格项目②:这个网格项目只指定了列(grid-column: 3;),所以它被放置在网格的第1行第3列,因为它是第3列的第一个空网格单元格
  • 网格项目③:在这种情况下,网格项目需要2行(grid-row: span 2;),它被放置在网格的第2~3行和第1
  • 网格项目④:它需22列(grid-row: span 2;grid-column: span 2;),因它有一些空的网格单元格,它被放置在网格的第2~3行和第2~3
  • 网格项目⑤:它被放置在当前网格的第4行第1

为什么网格项目⑤在新的一行而不是新的一列?这主要是由 grid-auto-flow 属性控制,下面会详细阐述。

grid-auto-flow 基本语法规则

grid-auto-flow 控制了网格项目自动放置算法的工作方式,指定了自动放置的网格项目是如何流入网格。该属性使用语法规则很简单:

grid-auto-flow: [ row | column ] || dense

其中 row 是其初始值,而且 rowcolumn 可以与 dense组合使用,即:

grid-auto-flow: row | column | dense | row dense | column dense

具体值的含义:

  • row :自动放置算法通过依次填充每一行来放置网格项目,必要时添加新行。如果既没有提供行也没有提供列,则假定是行
  • column :自动放置算法通过依次填充每一列来放置网格项目,必要时添加新的列
  • dense :如果指定,自动放置算法使用 dense (“密集”)包装算法,如果较小的网格项目出现在网格中,它将尝试在较早的时间内填入洞(网格单元格)。这可能会导致网格项目不按顺序出现,而这样做会填补大网格项目留下的洞(网格单元格)。如果省略,则使用sparse(“稀疏”)算法,自动放置算法在放置网格项目时只在网格中“向前(forward)”移动,从不回溯以填补漏洞。这确保了所有自动放置的网格项目都是“按顺序”出现的,即使这留下了可以由后来的网格项目填补的洞。

请注意,dense只是改变了网格项目的视觉顺序,可能会导致它们出现失序,这对可访问性是不利的。

先来看一个有关于 grid-auto-flow 的示例:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 100px);
    gap: 10px;

    --grid-auto-flow: row;
    grid-auto-flow: var(--grid-auto-flow);
}
.grid__item:nth-child(1) {
    grid-row: span 2;
    grid-column: 2;
}
.grid__item:nth-child(2) {
    grid-column: span 2;
}

尝试着改变示例中 grid-auto-flow 的值,你可以看到下面这样的变化:

注意,grid-auto-flow 属性会用到自动放置语法,有关于这部分稍后会详细阐述。

上面的示例只是简单地向大家演示了grid-auto-flow不同值给网格项目自动放置带来的变化。但网格项目自动放置是有一些规则存在的。我们先从默认的规则开始。

自动放置的默认规则

在 CSS 网格布局中,自动放置有一个最基本的规则:

网格中的网格项目会把自己摆放到网格中,每一个网格单元格会有一个网格项目。默认的流向是按行排列网格项目。

比如下面这个示例,使用grid-template-columns 创建了一个三列的网格(网格行会随着网格项目增加而创建新的行):

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}

当我们添加新的网格项目,到第四个的时候会自动创建新的行:

在默认情况下,网格中被自动创建的隐式网格行轨道的尺寸是自适应大小的,也就是说它们会包含所有属于它们的内容,而不会让内容溢出。不过,可以显式使用 grid-auto-rows 属性控制它们的大小。比如在上面的示例基础上添加 grid-auto-rows的值:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;

    --grid-auto-rows: auto;
    grid-auto-rows: var(--grid-auto-rows);
}

你在示例中改变 grid-auto-rows 的值,比如让网格轨道行的尺寸都是 100px

从上面两个示例中不难发现,不管是否显式使用 grid-auto-rows ,在默认情况下,自动放置是按行自动放置网格项目的。除此之外,网格也可以按列来自动放置网格项目。只需要在网格容器上显式设置 grid-auto-flow 的值为 column 就可以让网格项目按列自动放置。此时,网格容器中的网格项目将根据已定义的 grid-template-columns 按列摆放网格项目,当显式网格中的列全部排满之后,网格会自动在显式网格最后一列的末端创建新的列。

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-flow: column;
    gap: 10px;
}

这个示例使用 grid-template-columns 创建 3 x 1 的网格,每列宽度是 1fr。正如示例中所示,新增网格项目之后,当有第四个网格项目时,会创建新的网格列出来(隐式网格列轨道):

这个示例和前面的示例不同之处是因为我们在网格容器上显式设置了 grid-auto-flow的值为columns,网格项目自动放置是按列放置。同样的地,也可以像 grid-auto-rows 那样通过grid-auto-columns 来控制隐式网格轨道的列尺寸:

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;

    --grid-auto-columns: auto;
    grid-auto-columns: var(--grid-auto-columns);
    grid-auto-flow: column;
}

可以尝试着改变示例中 grid-auto-columns 的值:

在网格布局中,网格首先要做的是让每一个网格项目都有一个位置。但有的时候为了让网格项目位置能放到指定位置,我们会显式的使用 grid-rowgrid-columngrid-area 或者结合 span 来明确指定网格项目的位置(一般基于网格线名称,grid-area也可以基于网格区域名称,span可以用来合并网格单元格)。比如下面这个示例:

.grid__container {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: 100px;
    grid-gap: 10px;
}

.grid__item:nth-child(2) {
    grid-column: 3;
    grid-row: 2 / 4;
}

.grid__item:nth-child(5) {
    grid-column: 1 / 3;
    grid-row: 1 / 3;
}

仔细观察示例中的第二个和第五个网格项目和那些自动放置的网格项目。自动放置的网格项目将按 DOM 中的顺序从已明确指定位置的网格项目的前面开始摆放,虽然有两个网格项目已经事先指定好位置,但其他网格项目不是从已经定位的网格项目之后才开始摆放的。

上面示例中的第五个网格网项目占了两行两列,第二个网格项目占了两行,而且这两个网格项目都是基于网格线名称指定了明确的放置位置。但有的时候,网格项目在明确指定网格项目位置的时候会造成一定的缺口出现。比如下面这个示例:

.grid__container {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: 100px;
    grid-gap: 10px;
}

.grid__item:nth-child(4n+1) {
    grid-column-end: span 2;
    grid-row-end: span 2;
}
.grid__item:nth-child(2) {
    grid-column: 3;
    grid-row: 2 / 4;
}
.grid__item:nth-child(5) {
    grid-column: 1 / 3;
    grid-row: 1 / 3;
}

示例中的网格第3列第一个网格单元格和第4列第一、二单元格空出来了,它们也被称为网格洞(或网格缺口):

在网格中会产生这样的网格缺口是因为对于自动放置的网格项目,如果网格轨道的大小不适合放入一个网格项目,这个网格项目就会被移动到下一行,直到它找到了可以容纳它的空间。

在实际使用网格布局时,我们是不希望有上面示例这种现象产生,即产生网格缺口。如果你想避免这种现象产生,需要在网格容器上显式使用 grid-auto-flowdense 值:

.grid__container {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: 100px;
    grid-auto-flow: dense;
    grid-gap: 10px;
}

这两个示例的对比效果(grid-auto-flow 有没有设置值为dense):

网格项目自动放置的示例

我们来看一个网格项目自动放置的示例,该示例来自于 W3C 规范,即一个表单布局效果。是一个有三列的网格,每一列的大小都与它们的内容(auto)一致。在网格中没有显式定义行,不过使用grid-auto-flow的默认值row,它指示网格项目从网格第一行开始,并在其三列中搜索,然后是下一行,并且会根据需要增加行,直到找到足够空间来容纳任何自动放置的网格项目的位置。

<!-- HTML -->
<form>
    <label for="firstname">First name:</label>
    <input type="text" id="firstname" name="firstname" />
    <label for="lastname">Last name:</label>
    <input type="text" id="lastname" name="lastname" />
    <label for="address">Address:</label>
    <input type="text" id="address" name="address" />
    <label for="address2">Address 2:</label>
    <input type="text" id="address2" name="address2" />
    <label for="city">City:</label>
    <input type="text" id="city" name="city" />
    <label for="state">State:</label>
    <select type="text" id="state" name="state">
        <option value="WA">Washington</option>
    </select>
    <label for="zip">Zip:</label>
    <input type="text" id="zip" name="zip" />

    <div id="department-block">
        <label for="department">Department:</label>
        <select id="department" name="department" multiple>
        <option value="finance">Finance</option>
        <option value="humanresources">Human Resources</option>
        <option value="marketing">Marketing</option>
        </select>
    </div>

    <div id="buttons">
        <button id="cancel">Cancel</button>
        <button id="back">Back</button>
        <button id="next">Next</button>
    </div>
</form>

/* CSS */
form {
    display: grid;
    grid-template-columns: [labels] auto [controls] auto [oversized] auto;
    grid-auto-flow: row dense;
    gap: 10px;
}
form > label {
    grid-column: labels;
    grid-row: auto;
    align-self: center;
}
form > input,
form > select {
    grid-column: controls;
    grid-row: auto;
}

#department-block {
    grid-column: oversized;
    grid-row: span 3;
}

#buttons {
    grid-row: auto;
    grid-column: 1 / -1;
    text-align: end;
}

网格审查器查看上面示例中网格的相关参数描述:

待续...

文章主要和大家探讨了 CSS 网格布局中 grid-auto-flow 的语法规则和自动放置网格项目规则。CSS 网格布局的 grid-auto-flow 属性提供了三种不同语法规则,即 rowcolumndense 等布局方法来处理网格项目自动放置。但在网格布局中,网格项目自动放置语法还有一些其他的规则,不过将这部分内容放到介绍网格项目的章节中来介绍。另外,到这节为止,运用于网格容格容器的属性,除了网格项目对齐方式、subgrid 和 瀑布流布局之外基本上介绍完了,但在网格容器上的特性还有很多,比如说网格线的创建、命名等。下一节就将和大家一起探讨网格线相关的特性。感兴趣的同学请持续关注相关的更新