11.13 CSS 网格布局定位

用来确定网格布局中元素的摆放位置

每一个网格项目都有一个矩形的网格区域,这个网格区域定义了该网格项的包含块,对齐属性(justify-self 和 align-self)确定其实际位置。 网格项占用的单元格也会影响网格的行和列的大小,参看网格尺寸(Grid Sizing)

网格区域在网格内的位置通过1个网格位置和1个网格跨度属性来定义:

grid position
表示网格项在网格中的位置。该属性可以是确定的(被显式定义)或自动的(auto-placement)。
grid span
网格项占用的网格有多少个。子网格的跨度可以是自动的(由其隐式网格确定),除此之外网格跨度总是确定的(默认为1)。

网格布局属性grid-row-start, grid-row-end, grid-column-start, grid-column-end, 及其速写方式grid-row, grid-column, 和grid-area,允许作者通过以下信息(任意一个或没有)来指定:

Row Column
Start 行起始线 列起始线
End 行结束线 列结束线
Span 行跨度 列跨度

在同一维度上的Start, End, 和 Span属性,只要给定其中2个,就确定了第3个值。

下表总结了在哪些情况下一个网格位置或跨度是确定的(definite)还是自动的(automatic)

Position Span
Definite 至少有1个指定网格线 显式,隐式或缺省跨度(非子网格缺省值为1)
Automatic 没有显式指定的网格线 没有显式或隐式定义的子网格(subgrid)

网格定位的常用模式

网格定位属性被分成3组速写方式:

grid-area
grid-column grid-row
grid-column-start grid-column-end grid-row-start grid-row-end

命名区域(Named Areas)

网格项可以通过指定网格区域的名称而放进一个命名网格区域中(比如在网格模板区域(grid-template-areas)中所定义的模板):

article {
  grid-area: main;
  /* 把article元素放进名为“main”的命名区域中。 */
}

网格项可以和多个网格区域对齐,通过指定起点和终点和某区域对齐:

.one {
  grid-row-start: main;
  /* 将行开始边缘对齐到名为“main”的命名区域的开始边缘。 */
}

数字索引和跨度(Numeric Indexes and Spans)

网格项可以通过数字来定位和确定大小,这对于脚本化布局特别有用:

.two {
  grid-row: 2;    /* 放到第二行。 */
  grid-column: 3; /* 放到第三列。 */
  /* 等同于规则 grid-area: 2 / 3; */
}

缺省情况下,网格项的跨度为1。我们可以显式指定跨度:

.three {
  grid-row: 2 / span 5;
  /* 从第二行开始,向下横跨5行(在第7行结束)。 */
}

.four {
  grid-row: span 5 / 7;
  /* 在第7行结束,向上倒着横跨5行(以第2行为开始)。 */
}

注意:网格索引和书写模式(writing mode)相关,比如在一个从右往左的语言Arabic中,第一列是最右边的列。

命名线和跨度(Named Lines and Spans)

除了数字序号外,我们也可以用容易记住的名称来引用网格线:

.five {
  grid-column: first / middle;
  /* Span from line "first" to line "middle". */
}

注意:如果一个命名网格区域和一个命名线有相同的名称,则定位算法将优先使用名为网格区域的边缘。

如果有多个相同名称的网格线,它们将建立一个命名网格线组,可以通过名称加序号的方式来选择:

.six {
  grid-row: text 5 / text 7;
  /* 横跨第五和第七个命名为“文本”的网格线之间的区域。 */
  grid-row: text 5 / span text 2;
  /* 同上。 */
}

自动布局(Auto Placement)

一个网格项可以被自动放置到下一个可用的空网格单元,如果没有剩余空间的话,网格将相应扩展。

.eight {
  grid-area: auto; /* 初始值 */
}

这可以用来在一个电商网站上以网格模式列出一些在售商品。

如果网格项占用多个单元格,自动布局可以和一个指定的跨度结合起来使用:

.nine {
  grid-area: span 2 / span 3;
  /* 自动放置的网格项,覆盖2行和3列。 */
}

自动布局算法搜索和扩展行,还是搜索和扩展列,这由网格自动流(grid-auto-flow)属性控制。

注意:默认情况下,自动布局算法呈现线性查找而不出现回溯;如果它必须跳过一些空白处以放置一个较大的项目,它将不会返回填充这些空间。要改变这种行为,可以在网格自动流中指定dense关键字。

自适应尺寸的子网格(Auto Sizing Subgrids)

一个没有明确定义跨度的子网格将适配其隐式网格,即首先把其所有的网格项放置到自己的网格中,这样其跨度由最终的网格尺寸确定。

例如,一个跨越两列的子网格可以给定一个自动跨度的块大小:

.adverts {
  grid: subgrid;
  grid-column: span 2;
}

如果它包含10个自动放置的网格项,将在其父网格中占据5行。

网格项布局和代码序

网格布局属性的能力允许内容可自由整理和排列在网格里,这样在视觉呈现上可以和背后的文档代码顺序解耦合。 作者有很大的自由度来裁剪页面以渲染到不同的设备和显示模式,例如使用媒体查询(media query)。但它们不能代替正确的代码顺序。 正确的代码顺序对于序列化浏览以及无样式用户代理(如搜索引擎、语音阅读、触觉浏览器等)是重要的。网格布局仅仅影响视觉呈现!

基于网格线的布局属性:grid-row-start,grid-column-start,grid-row-end,grid-column-end

名称: grid-row-start, grid-column-start, grid-row-end, grid-column-end
取值: <grid-line>
初始值: auto
适用于: 网格项和包容块为网格容器的绝对定位盒子
继承性: no
百分比: n/a
媒体: visual
计算值: 指定值
动画性: no
<grid-line> =
  auto |
  <custom-ident> |
  [ <integer> && <custom-ident>? ] |
  [ span && [ <integer> || <custom-ident> ] ]

grid-row-start, grid-column-start, grid-row-end 和 grid-column-end 属性通过给出一个网格线或跨度来确定网格项的大小和位置。

取值的含义如下:

<custom-ident>
首先尝试匹配命名网格区域的边缘:如果存在名为<custom-ident>-start(对于grid-*-start)或<custom-ident>-end(对于grid-*-end)的网格线,则可确定网格项布局的第一条边缘。 如果没有,则被视作指定了整数1和custom-ident一起的情况。
<integer> && <custom-ident>?
确定由<integer>指定的第N条网格线,如果给定一个负数,则从显式网格的结束边反过来计算。 如果同时指定了名称<custom-ident>,则只计入该名称的线。如果线条数目不够,所有隐式网格线被假设为拥有该命名加入定位计算。零代表无效取值。
span && [ <integer> || <custom-ident> ]
该取值将给定一个网格跨度,网格项的两条边间距N条线。如果给定了<custom-ident>的名称,则只计入该名称的线。如果线条数目不够,所有隐式网格线被假设为拥有该命名加入跨度计算。如果<integer>被忽略,则取缺省值1。负数和零为无效值。

auto
该取值表示使用自动定位和跨距或默认跨距。

给定一个单行,8列的网格和9个命名线,如下:

1  2  3  4  5  6  7  8  9
+--+--+--+--+--+--+--+--+
|  |  |  |  |  |  |  |  |
A  B  C  A  B  C  A  B  C
|  |  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+--+

以下声明将网格项放置到索引指示的网格线之间:

grid-column-start: 4; grid-column-end: auto;
/* Line 4 to line 5 */

grid-column-start: auto; grid-column-end: 6;
/* Line 5 to line 6 */

grid-column-start: C; grid-column-end: C -1;
/* Line 3 to line 9 */

grid-column-start: C; grid-column-end: span C;
/* Line 3 to line 6 */

grid-column-start: span C; grid-column-end: C -1;
/* Line 6 to line 9 */

grid-column-start: span C; grid-column-end: span C;
/* 错误声明:end span被忽略,auto-placed的项不能横跨到一个命名线,将被等同于grid-column: span 1;。 */

grid-column-start: 5; grid-column-end: C -1;
/* Line 5 to line 9 */

grid-column-start: 5; grid-column-end: span C;
/* Line 5 to line 6 */

grid-column-start: 8; grid-column-end: 8;
/* 错误声明:将被等同于line 8 到 line 9 */

grid-column-start: B 2; grid-column-end: span 1;
/* Line 5 to line 6 */

网格布局(placement)冲突处理

如上面的例子所示,有些声明存在冲突或错误。如果网格项布局包含两个网格线,而开始线要在结束线的后面,那么两者将互换。

如果包含两个跨度定义,去除*-end布局属性中的那个。如果仅包含一个命名线的span,则替换为span 1。

网格布局属性速写(Placement Shorthands):grid-column,grid-row和grid-area

姓名: grid-row, grid-column
取值: <grid-line> [ / <grid-line> ]?
初始值: 参见单独属性
适用于: 网格项和包容块为网格容器的绝对定位盒子
继承性: 参见单独属性
百分比: 参见单独属性
媒体: visual
计算值: 参见单独属性
动画性:

grid-row 和 grid-column 属性分别是grid-row-start/grid-row-end 和 grid-column-start/grid-column-end的速写。

如果指定了2个<grid-line>,grid-row-start/grid-column-start被设置为/符号之前的值,而grid-row-end/grid-column-end被设置为/符号之后的值。

当第二个值未指定时,如果第一个值是一个自定义名称(<custom-ident>),grid-row-end/grid-column-end也被设置为那个名称,否则,被设置为auto。

姓名: grid-row, grid-column
取值: <grid-line> [ / <grid-line> ]{0,3}
初始值: 参见单独属性
适用于: 网格项和包容块为网格容器的绝对定位盒子
继承性: 参见单独属性
百分比: 参见单独属性
媒体: visual
计算值: 参见单独属性
动画性:

如果指定了4个<grid-line>值,grid-row-start被设置为第一个值,grid-column-start被设置为第2个值,grid-row-end被设置为第3个值,grid-column-end被设置为第4个值。

当grid-column-end被忽略时,如果grid-column-start是一个<custom-ident>,grid-column-end也被设置为该<custom-ident>;否则,被设置为auto。

当grid-row-end被忽略时,如果grid-row-start是一个<custom-ident>,grid-row-end也被设置为该<custom-ident>;否则,被设置为auto。

当grid-column-start被忽略时,如果grid-row-start是一个<custom-ident>,所有4个常规属性被设置为该值。否则,被设置为auto。

注意:速写属性的解析顺序为row-start/column-start/row-end/column-end。

网格项定位算法

下面的网格项布局算法把网格项自动定位解析为确定位置,确保每一个网格项有一个明确的网格区域可供摆放。 (网格跨度不需要特别的处理,如果它们没有明确指定,默认为1,或者对于子网格而言是其显式网格的大小。)

注意:如果在显式网格中没有空间放置一个自动定位的网格项,该算法将导致在隐式网格中创建新的行或列。

每个网格单元(包括显性和隐性网格)可以被占用或空置。当由一个命名网格区域,或由具有明确位置的网格项的网格区域所覆盖时,网格单元是被占用的;否则,该单元是空置的。 网格单元的占用和空置状态可以在该算法计算中发生变化。

为了表述更清晰,下面的算法假设网格自动流(grid-auto-flow)属性被设置为row。 如果它被设置为column,则交换该算法中所有行和列,行内和块等相关描述。

  1. 定位所有非自动定位的元素。

  2. 处理固定到指定行的网格项。

    对于每一个具有确定行位置的网格项目(也就是网格行开始(grid-row-start)和网格行结束(grid-row-end)属性定义了明确的网格位置),以修改后的文档序
    sparse组装(忽略dense关键词) 将column-start线设置为最早的(最小的正指数)行索引,以确保该项目的网格区域不会覆盖任何已被占用的网格单元和先前通过该步骤放置在该行中的网格项。
    dense组装(指定dense关键词) 将column-start线设置为最早的(最小的正指数)行索引,以确保该项目的网格区域不会覆盖任何已被占用的网格单元。

  3. 确定隐含网格中的列数。

    将隐式网格中的列数设置为下列几个数值中的较大者:

    • 显式网格中的列数。

    • 在全部有明确列位置的网格项(明确定位的网格项,在前一步骤中已定位的,以及尚未定位的网格项但有一个明确的列)中,最大的column-end网格线索引值,减1。

    • 所有网格项的最大列跨度。

    #grid {
      display: grid;
      grid-template-columns: repeat(5, 100px);
      grid-auto-flow: row;
    }
    #grid-item {
      grid-column: 4 / span 3;
    }
    

    上面的网格项所需的列数为6。#grid-item元素的列开始(column-start)线是指数4, 那么其跨度保证它列结束(column-end)线将为索引7。这需要7 - 1 = 6列来包含该项目,这大于显式网格的5列。

  4. 定位剩余的网格项

    auto-placement游标(cursor)定义在网格中的当前“插入点”,指定为一对行和列的网格线。 auto-placement游标的初始值为行和列的位置都等于1。

    grid-auto-flow的值用来确定以下项目:

    稀疏组装(忽略了dense关键词)

    对于未经前几步定位的每一个网格项目,以修改后文档序(order-modified document order)来定位:

    如果该项目包含一个明确的列位置:

    1. 将游标的列位置设置为网格项目的列开始行索引。如果这个位置小于前一个列的游标位置,该行位置加1。

    2. 增加游标的行位置,直到找到一个值,该值使得网格项不与任何被占用的网格单元重叠(在必要时创建新的行)。

    3. 将该项目的行开始(row-start)线设置为游标的行位置。(同样,根据其跨度隐式设置项目的行结束线。)

    如果该项目在两个轴向上都有一个自动网格位置:

    1. 增加auto-placement游标的列位置,直到这个项目的网格区域不与任何已占用的网格单元重叠,或者游标的列位置,再加上该项目的列跨度,超出了隐式网格的列数,如之前在该算法所确定的那样。

    2. 如果在前一步中已找到一个非重叠位置,设置该项目的行开始和列开始线到游标的位置。否则,增加自动放置(auto-placement)游标的行位置(在必要时创建隐式网格中的新行),将其列位置设置为1,然后返回到前一步。

    密集组装 (指定了dense关键词)

    对于未经前几步定位的每一个网格项目,以修改后文档序(order-modified document order)来定位:

    如果该项目包含一个明确的列位置:

    1. 将游标的行位置设置为1。将游标的列位置设置为网格项的列开始(column-start)线索引。

    2. 增加游标的行位置,直到找到一个值,该值使得网格项不与任何被占用的网格单元重叠(在必要时创建新的行)。

    3. 将该项目的行开始(row-start)线设置为游标的行位置。(同样,根据其跨度隐式设置项目的行结束(row-end)线。)

    如果该项目在两个轴向上都有一个自动网格位置:

    1. 将游标的行和列位置设置为1。

    2. 增加auto-placement游标的列位置,直到这个项目的网格区域不与任何已占用的网格单元重叠,或者游标的列位置,再加上该项目的列跨度,超出了隐式网格的列数,如之前在该算法所确定的那样。

    3. 如果在前一步中已找到一个非重叠位置,设置该项目的行开始和列开始线到游标的位置。否则,增加自动放置(auto-placement)游标的行位置(在必要时创建隐式网格中的新行),将其列位置设置为1,然后返回到前一步。

绝对定位(Absolute Positioning)

有一个网格容器作为包含块

如果一个绝对位置的元素的包含块是由一个网格容器生成的,包含块对应于由其网格布局(grid-placement)属性所确定的网格区域。这样偏移属性 (top/right/bottom/left)表示此包含块对应边缘的内偏移量。

注意:尽管把元素绝对定位到网格容器时,允许它与容器的网格线对齐,但这样的元素不占用空间或以其他方式参与网格的布局。

.grid {
  grid: 10rem 10rem 10rem 10rem / 1fr 1fr 1fr 1fr;
  /* 4 columns of 10rem each,
     4 equal-height rows filling the grid container */
  justify-content: center;
  /* center the grid horizontally within the grid container */
  position: relative;
  /* Establish abspos containing block */
}

.abspos {
  grid-row-start: 1;     /* 1st grid row line = top of grid container */
  grid-row-end: span 2;  /* 3rd grid row line */
  grid-column-start: 3;  /* 3rd grid col line */
  grid-column-end: auto; /* right padding edge */
  /* Containing block covers the top right quadrant of the grid container */

  position: absolute;
  top: 70px;
  bottom: 40px;
  left: 100px;
  right: 30px;
}

注意:网格和网格布局属性和文档流模式相关,而abspos的偏移是物理尺寸,所以如果文档流解析方向或写作模式发生变化,网格将相应调整以匹配新的模式,但偏移值不会。

grid-placement属性当取值为auto时将给位于相应网格容器填充边缘(padding edge)位置的布局确定一个特别网格线。这些线成为用于定位绝对定位网格项的增强网格的第一和最后一行(0和-0)。

注意:因此,默认情况下,绝对定位框的包含块将契合网格容器的填充边缘,如同块容器一样。

绝对定位发生在网格布局及其文档流内(in-flow)内容之后,并且不会对任何网格轨道的尺寸产生贡献或以任何方式影响网格的大小/设置。 如果一个网格布局属性指向一个不存在的行,通过显式指定这样一条线或跨度超出现有隐式网格之外,那么它将被指定为自动(而不是创建新的隐式网格)。

如果布局仅包含一个网格跨度,在该轴上把它替换为两条自动线。

有一个网格容器作为父元素

一个绝对定位的网格容器子元素的静态位置,其位置的确定就好象它是网格区域中的唯一网格项,该区域边缘与网格容器的填充边缘重合。 然而,如果网格容器父元素也是绝对定位元素包含块的生成器,则使用上面“有一个网格容器作为包含块”来确定网格区域。

注意:这个位置受子元素上的自我整理(justify-self)和自我对齐(align-self)属性的影响,并且,和大多数其他布局模型一样,绝对定位的子元素不会影响包含块的大小或其内容的布局。

对齐(Alignment)和空白(Spacing)

当网格容器的网格轨道以及所有网格项的尺寸计算完成后,就可以设置网格项在其网格区域内的对齐属性。

和块布局一样,margin属性可以用来对齐网格项,但功能要更加强大。网格项的对齐属性也同样遵循框对齐规范,支持纵横两个轴向上基于关键词的对齐方式。

默认情况下,网格项延伸到填充网格区域。但是,如果justify-self或align-self的计算值不是stretch或者margins是自动计算的,网格项将自动调整大小以匹配其内容。

间隔(Gutters):grid-column-gap, grid-row-gap 和 grid-gap 属性

grid-column-gap和grid-row-gap分别指定网格列和行之间的间隔。效果就好比指定尺寸的额外的网格轨道。grid-gap是两者的速写方式。

使用自动留白(auto margin)对齐

网格项的自动留白和块布局中很类似:

  • 在计算网格轨道大小时,auto margin被视为0。
  • 自动留白在对齐之前吸收正的空置空间。
  • 溢出元素忽略它们的auto margins,并且在end方向溢出。

注意:如果空置空间被分配到auto margins,对齐属性将在该维度中没有效果,因为margin将占用全部尺寸计算后的遗留的空置空间。

行轴对齐:justify-self和justify-items属性

网格项可以在行内(inline)维度上对齐,通过在网格项上使用自我整理(justify-self)属性,或者在网格容器上使用justify-items属性。参见规范定义:[CSS3-ALIGN]

列轴对齐:align-self和align-items属性

类似的,网格项可以在块(block)维度上对齐(垂直于内联维度),通过在网格项上使用自我对齐(align-self) 属性,或者在网格容器上使用align-items属性。

网格对齐:justify-content和align-content属性

如果网格的外边缘不对应于网格容器的内容边缘(例如,如果没有任何列是弹性大小(flex-sized)的话),网格轨道根据网格容器的内容整理(justify-content)内容对齐(align-content)属性在内容框内对齐。

比如,下面的网格垂直居中,并和网格容器的右边对齐:

.grid {
  display: grid;
  grid: 12rem 12rem 12rem 12rem / 10rem 10rem 10rem 10rem;
  justify-content: end;
  align-content: center;
}

网格基线(Grid Baselines)

一个网格容器的基线确定如下:

  1. 如果参与对齐的任意网格项的网格区域和网格容器第一行/列相交,网格容器的基线就是这些网格项的基线。.
  2. 否则,如果网格容器中有至少一个网格项目的区域与第一行/列相交,而该网格项具有平行于相应轴的基线,则网格容器的基线就是该基线。
  3. 否则,网格容器的基线从第一个(在修改后的网格序中)网格项的内容框获得,如果没有,则从网格容器的内容框(content box)获得。

我们说一个网格项参与某个轴向的基线对齐,如果它的align-self或justify-self属性的值为baseline并且其行内轴和该维度平行。

修改后的网格序(order-modified grid order)是遍历网格单元的顺序,如果计算行内轴基线是行为主(row-major)顺序,如果计算块轴基线则是列为主(column-major)顺序。如果同时遇到2个网格项,则采用修改后的文档序(order-modified document order)。

根据上述规则计算基线时,如果基线框有一个允许滚动的overflow值,则该框必须被视为其初始滚动位置以确定其基线。

当确定表格单元的基线时,网格容器提供了一个类似于行框(line box)或表格行(table-row)的基线。

参考阅读:CSS3 Grid Layout W3规范