WebGL Tutorial
and more

CSS Grid入门教程

撰写时间:2023-05-29

修订时间:2023-06-11

CSS Grid是极富创造性的一种页面布局编排机制,是CSS不断进化过程中的集大成者。它丢弃了HTMLtable标签的呆板机制,却紧密地结合CSS各种最新技术,从CSS的角度,重新实现了table的功能,并有过之而无不及。

CSS Grid的内容较多,学习起来有一定的难度,本教程通过丰富的实例,并借助于本站自行开发的强大的LiveEditor的实时运行功能,清晰地演示了CSS Grid的各项关键技术,能较为有力地帮助我们系统学习与掌握CSS Grid技术。

本教程覆盖了CSS Grid 1.0版本的内容。

本教程使用下面的CSS通用设置:

div.grid-container { background: repeating-linear-gradient(45deg, #333333 0, #333333 5%, #4f4f4f 0, #4f4f4f 50%) 0 / 10px 10px; } div.grid-container div { border: 1px solid gray; padding: 0.5em; background-color: #333; }

这样可更清楚地看到CSS Grid的效果。

如果需要在某个LiveEditor中改变这些默认值,则根据CSS的规则,可在LiveEditorCSS标签页中添加诸如下面的代码:

body div.grid-container div { padding: 1em; }

因为使用了更长、更具体的选择器,因此上面的CSS设置将比通用设置有更高的优先权限。

CSS Grid父容器的设置

A
B
C
D
E
div.grid-container { display: grid; grid-template-columns: 100px 1fr; gap: 0.5em; }

观察HTML标签页中的内容,父容器共有5个一级子元素。由于这些子元素是块级元素,默认情况下,它们将各占一行,依序从上到下排列。

现在,将CSS标签页中的内容全部删除,点击Run按钮,可看到这种默认的排版情况。之后,重新刷新页面,恢复CSS标签页中的初始设置内容。

应用CSS Grid的第一步,是将父容器的display属性设置为grid,这样,父容器下面的所有第一级子元素将由Grid统一进行管理。CSS Grid的本质在于智能地、灵活地指定Grid父容器下各个一级子元素的行、列位置。

第二步,设置grid-template-columns属性值。取名为template-columns的含义为,在决定每一行中各个子元素应位于第几列时,均按此模板来设置。上面,该属性值被设定为100px 1fr,意为每行只排2个子元素。第1个子元素的宽度为固定的100px;第2个子元素的宽度属性为1fr,则会占据该行中所有未使用的空间(这种空间,我们可称之为可用的弹性空间)。

排完第一行的元素后,还剩3个子元素。因为第一行已经没有空间,因此这3个子元素继续依照grid-template-columns: 100px 1fr的设置,依序在第2行、第3行中编排。第3行因为只有1个固定宽度的元素,因此右边还剩余许多弹性空间,其背景是斜纹灰底。

从左到右,依照模板格式编排子元素的各列,这是CSS Grid的最原始的设计理念。

最后一行的gap,可用以设置各个子元素行与列之间的间距。

练习:在LiveEditor中将grid-template-columns属性值设为50px 1fr 100px 25px,然后运行代码。查看结果,体会各种属性值的作用。

fr的作用

fr有两种格式:一是诸如3fr前面的数值大于等于1的格式;二是诸如0.3fr前面的数值小于1的格式。下面分别考察。

数值大于等于1的fr

div.grid-container { display: grid; grid-template-columns: 1fr 2fr 30px 3fr; }
A
B
C
D
E

fr是可自动变长的数值。上面代码,grid-template-columns属性值为1fr 2fr 30px 3fr,则CSS Grid先在该行中优先确定C元素的宽度,然后,将该行所剩的空间在其他几个具有fr属性值的子元素之间再进行分配。

fr意为部分,数值为百分比的形式,我们也可以理解为单位。上面的例子中,第1个子元素占1个单位的宽度,第2个子元素占2个单位的宽度,第3个子元素占3个单位的宽度。因此,B的宽度是A的2倍,CA的3倍。

1fr确定了分子为1,分母呢?分母为该行中所有具有fr属性值的子元素的数值总和。因此,上面分母为1 + 2 + 3 = 6,则A元素宽度为除去C元素宽度之外的1/6B元素宽度为2/6C元素宽度为3/6。如果一行只有一个具有fr属性值的子元素,则不管其数值是多少,都会占去所有的弹性空间。因此,它与n%的算法不一样。

我们可以这样思考。先确定一个1fr的元素,然后在剩下的空间中,按照其他元素是该元素的几倍来确定。如A元素的宽度为1fr,如果B元素应是A元素的2倍,则取2fr。余此类推。

数值小于1的fr

我们可以编写0.1fr的代码,即fr的前面是小于1的小数。这种方式类似于n%的效果。下面看各个小数之和的情况。

和等于1

在理想的情况下,一行中各个元素小数点之和应等于1。

div.grid-container { display: grid; grid-template-columns: 0.2fr 0.8fr; }
A
B
C
D
E

此时,各元素能自动铺满所在行的可用弹性空间。

和小于1

div.grid-container { display: grid; grid-template-columns: 0.2fr 0.6fr; }
A
B
C
D
E

此时,各元素按指定的比例占用所在行的可用弹性空间,但留下了0.2的可用弹性空间未使用。

既然仍有可用弹性空间,再另上一个固定宽度的元素呢?

div.grid-container { display: grid; grid-template-columns: 0.2fr 0.6fr 100px; }
A
B
C
D
E

其结果是,先保证固定宽度的C元素的宽度,然后再将剩余的可用弹性空间按0.2、0.6、0.2的比例分配给A元素及B元素,由于各元素小数点之和未达到1,因此仍留下0.2的可用弹性空间。

和大于1

div.grid-container { display: grid; grid-template-columns: 0.9fr 0.3fr; }
A
B
C
D
E

由于总和超过1,此时,两个元素将占满所在行的可用弹性空间,且保持之间的比例0.9:0.3

这个效果,与grid-template-columns: 9fr 3fr;的效果完全一致。

因此,当出现两个超大的小数点,且这两个小数点的值又一样时,如:grid-template-columns: 0.9fr 0.9fr;,当您看到它们两个在如下所示的渲染时表现为平分可用弹性空间,就不会感到奇怪了。

div.grid-container { display: grid; grid-template-columns: 0.9fr 0.9fr; }
A
B
C
D
E

auto

auto也是一个会自动变长的属性值。但,与fr不同的是,auto有较多的细节需要注意。

单独使用auto

div.grid-container { display: grid; grid-template-columns: auto; }
A
B
C
D
E

当每一行只有一列,且其值为auto时,该列会自动铺满可用弹性空间。

div.grid-container { display: grid; grid-template-columns: auto auto auto; }
A
B
C
D
E

当一行有多个其值均为auto的列时,以每列宽度都相等的方式自动铺满可用弹性空间:

auto与固定宽度值共用

div.grid-container { display: grid; grid-template-columns: auto 100px; }
A
B
C
D
E

auto与固定宽度值共用时,该列会自动铺满剩余的可用弹性空间。

auto与fr共用

div.grid-container { display: grid; grid-template-columns: auto 2fr 2fr; }
A
B
C
D
E

autofr共用时,auto降格了,不再有弹性拉伸的作用,仅保留最小宽度,将其拉伸的优先权让位于fr,由fr来铺满剩余的可用弹性空间。

minmax

div.grid-container { display: grid; grid-template-columns: minmax(400px, 450px) 50px; }
A
B
C
D
E

minmax函数允许特定列在值域内取值。第1列的宽度最小值为400px,最大值为450px,第2列是固定的50px

如果您是在桌面电脑上访问此页面,则由于浏览器客户端有许多空间,而A元素最大值只有450px,因此每行会留出许多空白,透出我们特意设定的斜纹灰底(该部分为CSS Grid所管理的空间)。如果拉动浏览器边框以改变其宽度,当宽度越来小时,CSS Grid会优先使用多出的斜纹灰底空间,这样斜纹灰底空间会越来越小,从而确保尽可能使A元素获得最大值。而一旦多余空间耗尽,则开始减少A元素的宽度,但确保其宽度至少为400px。当继续减少浏览器的宽度,已经不能满足A元素与B都同时出现在可视区域内时,如果父容器允许,则出现水平滚动条,从而创建出更多的空间。

一句话,如果空间足够,则优先保障该元素的最大值;如果没有空间,至少保障该元素的最小值。

举个例子,我们对某人说,如果空间充裕,你也不要占得太多,得留出一些空间给别人;但如果空间太小,我可以保证你至少获得100平米的空间。

理解并掌握这个属性值,在分别为桌面及移动客户端布局时非常有用。我们可以为某个核心元素先设定一个必需的最小值,然后再设定一个合理的最大值。这样就不会因为空间大幅改变后而导致冒然地失去应用功能。

练习:在LiveEditor中将grid-template-columns属性值设为minmax(100px, 1fr) 50px;。这种布局,可让A列吃掉所有多余的空间,但又确保最小值。

还有专门的min函数及max函数,语义上更容易理解了。

min-content, max-content, fit-content

这3个属性值涉及到父容器如何包裹子元素的问题。

八卦中以五行生克来定吉凶。五行生克可以很简单,但蕴涵了许多朴素而深奥的哲学原理。

#wrapper { border: 1px solid yellow; }

外层div包裹了两个子元素,一个是img,另一个是p,它们都是块级元素。由于外层div同时也是块级元素,因此它独立占了一整行,从而导致图像及段落的右边都留下了不好看的空间。现在,我们的目标是,如何让外层容器自动紧密地包裹这些内部子元素?

设置外层容器的width属性值为max-content

八卦中以五行生克来定吉凶。五行生克可以很简单,但蕴涵了许多朴素而深奥的哲学原理。

#wrapper { border: 1px solid yellow; width: max-content; }

外层容器的宽度值取自于两个子元素中最大的宽度值,即段落的宽度值。与上面的结果相比较,段落右边是没有空间了,但图像右边还有,照样不好看。

设置外层容器的width属性值为min-content

八卦中以五行生克来定吉凶。五行生克可以很简单,但蕴涵了许多朴素而深奥的哲学原理。

#wrapper { border: 1px solid yellow; width: min-content; }

这时,外层容器的宽度值取自于两个子元素中最小的宽度值。两个子元素虽为块级元素,但它们还是存在区别的:图像的宽度不能自动改变,而段落的宽度则可以通过断行而自动改变。因此,此时则取图像的宽度作为外层容器的宽度值即可。

fit-content也是一个CSS属性名。它的意义在于,外层容器所使用的空间,不会大于max-content。因此,它的效果与max-content一样。

需注意的是,同时存在fit-content()CSS函数。

repeat函数

当多列的数值出现多次重复时,可使用repeat函数。

repeat(n, unit)

div.grid-container { display: grid; grid-template-columns: repeat(3, 80px); }
A
B
C
D
E

意为,重复3次80px的宽度,倍数放在前面,基本单元放在后面。因此,它与下面的代码是等效的:

div.grid-container { display: grid; grid-template-columns: 80px 80px 80px; }

repeat(n, unit unit)

div.grid-container { display: grid; grid-template-columns: repeat(2, 80px 50px); }
A
B
C
D
E

上面,每次需要重复两个宽度值:80px 50px,则可使用repeat(2, 80px 50px)的形式。

repeat auto-fill

repeat(auto-fill, unit)

div.grid-container { display: grid; grid-template-columns: repeat(auto-fill, 150px); }
A
B
C
D
E

上面的repeat(n, unit...)先确定每一列的具体宽度,再指定了每一行应有多少个这种宽度的列。因此,不管父容器的宽度是多少,每一行的列数是固定的。当该行没有空间而排不下这些元素时,则通过在父容器显示水平滚动条的方式来添加空间。

但我们往往还有另外一种需求,即,如果水平空间够宽敞,但希望排3个子元素,如果水平空间不够,则只需排2个子元素就行了。显然,这里子元素的个数是不确定的,因此不能使用上面的格式。repeat(auto-fill, unit)可用以解决这种特定的需求。

repeat(auto-fill, unit)含义是,一次排一个宽度为unit的元素,再看剩下的空间能否排得下第二个这种宽度的元素。如果剩下的空间排得下第二个,则将第二个元素排在第一个元素的右边。然后再看第三个、第四个, ...,依此类推。若遇到剩下的空间不足以排得下新的元素,则将新的元素排在第二行。在新行中,再重复上面的算法。

这样,只要我们指定的列宽,CSS Grid就会根据可用空间来自动安排在每行上的元素个数,从而既保证了列宽,又能在有限的空间内尽可能多地排上各个元素。

repeat(auto-fill, minmax)

上面我们指定每个元素为固定的150px的宽度,因此会造成每行右边会有留白。如果希望各元素还能自动铺满一行,则可以将每列的宽度指定minmax(150px, 1fr)

div.grid-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
A
B
C
D
E

这样,每列宽度不会少于150px,并且当每行右边的留白又不足以排下一个新元素时,则会平等的拉伸每个现有元素的宽度,以自动铺满整行的空间。

然而,如果每列的宽度太小,即使都排完了所有元素,auto-fill也不会自动铺满整行。如下所示:

div.grid-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(50px, 1fr)); }
A
B
C
D
E

repeat auto-fit

auto-fit可以解决上一节中的问题,即使每列的宽度太小而有留白,这些留白的宽度将变为0px,也即产生了塌陷(collapse),从而导致auto-fit可以自动拉伸每个元素的宽度以铺满整行。

div.grid-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(50px, 1fr)); }
A
B
C
D
E

综上,如果需要拉伸效果,而又无需考虑元素的最小宽度值是否过小,则应使用repeat(auto-fit, minmax())的代码。

grid-template-rows

grid-template-rows设定各行高度的模板。

指定模板行的数量少于总行数量的情况

div.grid-container { display: grid; grid-template-columns: 50px; grid-template-rows: 50px 20px 20px; }
A
B
C
D
E

上面子元素共有5行,而我们仅指定了3行,则第4行、第5行的高度取默认值。

我们使用grid-template-columns指定模板列时,如果模板列数少于所有子元素的数量,则会自动产生断行,新行中的子元素继续依照模板列的格式来编排。而grid-template-columns不会重复使用模板行的设置。如果我们指定的模板行不包含特定行,则特定行取默认高度值。

在许多情况下,总行的数量并非总是可以确定下来。那么如何自动涵盖所有的行?

使用grid-auto-rows为非模板行设置高度

grid-template-columns应对模板行,对于非模板行,可以使用grid-auto-rows来应对。

div.grid-container { display: grid; grid-template-columns: 50px; grid-template-rows: 50px 20px 20px; grid-auto-rows: 80px; }
A
B
C
D
E

上面最后两列为非模板行,通过设定grid-auto-rows: 80px,我们将所有这些自动调整的行的高度统一设置为80px

使用repeat来遍历所有的行

grid-template-columns可以使用repeat函数来遍历所有的行。

div.grid-container { display: grid; grid-template-columns: 50px; grid-template-rows: repeat(10, min-content); }
A
B
C
D
E

但这种方式,与不设置grid-template-rows一样。如果我们改为grid-template-rows: repeat(10, 50px);,则会看到,比总行数量多出的设置,会导致在下面产生了不必要的空间。这种方式不可取。

因此,还是像上节一样,使用grid-auto-rows: 80px;的方式更为稳妥。

CSS Grid布局术语详解

CSS Grid有多种精准定准子元素的方法,我们需要先了解CSS Grid相关术语,才能做到深刻理解随心所欲地予以编排。CSS Grid的结构示意图如下。

grid-lines

根据上面所学的知识,我们可以精准地用代码复现此示意图,并在相应的位置放置子元素。

div.grid-container { display: grid; grid-template-columns: 150px 1fr; grid-template-rows: 50px 1fr 50px; min-height: 250px; } body div.grid-container div { border: 1px dashed gray; }
A
B
C
D
E
F

共有A, B, C, D, E, F等6个子元素,每列排2个元素,故共有3行。

最外面的4条虚线所围成的区域称为grid,它用以整齐地排列各个子元素。图中的grid共有3行2列。

先看示意图中列的情况。用3条垂直竖线将每行分割为2列。示意图中,第1条垂直竖线用line 1表示,第2条垂直竖线用line 2表示,第3条垂直竖线用line 3表示。

同理,示意图使用4条水平横线将整个grid分割为3行。从上到下,其名称分别为line 1, line 2, line 3, line 4

对于这些分割行或列的虚线,我们统称为grid line。两条相邻的同向的grid line之间区域称为grid track。因此,它是一行或一列的统称。例如,在水平方向上,AB在同一grid track上,而在垂直方向上,A, C, E在同一grid track上。

这些grid lines将整个grid划分为6个区域,用于分别放置A, B, C, D, E, F等6个子元素。我们将这些可以放置子元素的最小区域称为grid cell。在上面的例子中,A, B, C, D, E, F均独立占具一个grid cell

若干个相邻的grid cell可以组成一个更大空间的grid area。搬用电子表格中的用语,grid area就是指若干个相邻的grid cell合并后的合并单元格。

Grid line的定义与引用

grid-row-startgrid-row-endgrid-column-start, 以及grid-column-end这4个属性需要引用具体的grid-line

使用Grid line的序号

每条Grid line都有自己的序号。从左到右及从上到下,序号从1开始,逐渐增大。也可以使用负数来表示倒数的序号,-1表示倒数第1行或倒数第1列,-2表示倒数第2行或倒数第2列,依此类推。Chrome, Safari, Firefox等浏览器的开发者工具可以查看CSS Grid的Grid line的序号。Chrome的效果如下图所示:

Grid line index

现在,我们准备将B排在1行1列,F排在2行2行。

div.grid-container { display: grid; grid-template-columns: 150px 1fr; grid-template-rows: 50px 1fr 50px; min-height: 250px; } body div.grid-container div { border: 1px dashed gray; } #b { grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2; } #f { grid-row-start: 2; grid-row-end: 3; grid-column-start: 2; grid-column-end: 3; }
A
B
C
D
E
F

第1行第1列这个grid cell所在的行由序号为12的两条grid line分割。因此,我们用grid-row-start: 1;来指定所在行从1这条grid line开始,用grid-row-end: 2;表示至2这条grid line结束。

同理,用grid-column-start: 1;来指定所在列从1这条grid line开始,用grid-column-end: 2;表示至2这条grid line结束。这样便达到精准定位的目标。

对于F元素而言,它的行始于第2条grid line,终于第3条grid line;它的列始于第2条grid line,终于第3条grid line

我们也可以使用负数的序号为子元素定位。例如,F元素也可这样指定:

#f { grid-row-start: -3; grid-row-end: -2; grid-column-start: -2; grid-column-end: -1; }

上面,我们显式地指定了BF元素的位置,CSS Grid将优先保证它们两个的位置,然后再将其他元素依照声明的次序,逐个排进模板列及模板行中。

为Grid line命名

除了直接引用grid line序号之外,我们还可以为grid line取名,在CSS Grid中称为custom ident,即custom identifier自定义标识符)。

一般命名方法

div.grid-container { display: grid; grid-template-columns: [first-grid-line] 150px [second-grid-line] 1fr; } body div.grid-container div { border: 1px dashed gray; } #b { grid-row-start: 1; grid-row-end: 2; grid-column-start: first-grid-line; grid-column-end: second-grid-line; }
A
B
C
D
E
F

首先,在设置grid-template-columnsgrid-template-rows时,在相应的单元格左右或上下的grid line出现的地方,加入相应的名称,名称用[ ]包围起来:

div.grid-container { ... grid-template-columns: [first-grid-line] 150px [second-grid-line] 1fr; ... }

其次,子元素引用这些grid line的名称,但去掉定义名称时所加上的[ ]

#b { grid-row-start: 1; grid-row-end: 2; grid-column-start: first-grid-line; grid-column-end: second-grid-line; }

一般情况下,我们需要引用哪些grid line时,才需要为它们命名。不需要引用的,就没必要浪费时间给它们命名了。

grid-template-columnsgrid-template-rowsgrid line的名称可以重合,不用担心命名会起冲突,因此它们应用的语义环境不一样。

div.grid-container { display: grid; grid-template-columns: [first-grid-line] 150px [second-grid-line] 1fr; grid-template-rows: [first-grid-line] auto [second-grid-line] auto auto; } body div.grid-container div { border: 1px dashed gray; } #b { grid-row-start: first-grid-line; grid-row-end: second-grid-line; grid-column-start: first-grid-line; grid-column-end: second-grid-line; }
A
B
C
D
E
F

上面,我们直接从具体grid line的角度,将它们取名为第几条线,这当然比不上直接引用序号便捷,目的仅在于演示而已。当然,您可以根据自己的语义环境,为它们取更有意义的名称。

遵循约定的命名方法

现在,我们从子元素的角度,为围绕其的grid line命名。

div.grid-container { display: grid; grid-template-columns: [item-b-start] 150px [item-b-end] 1fr; } body div.grid-container div { border: 1px dashed gray; } #b { grid-row-start: 1; grid-row-end: 2; grid-column-start: item-b-start; grid-column-end: item-b-start; }
A
B
C
D
E
F

我们将排在B元素左右两边的两条grid line分别取名为item-b-startitem-b-end,表示这两条grid line分别是B元素的起止之线。这是以元素为核心的命名方法。

上面的命名方式以分别以-start-end作为后缀命名。CSS Grid欢迎这种命名方式,并约定,若以这种方式命名,在引用该名称时,无需引用其后缀部分。

div.grid-container { display: grid; grid-template-columns: [b-start] 150px [b-end] 1fr; } body div.grid-container div { border: 1px dashed gray; } #b { grid-row-start: 1; grid-row-end: 2; grid-column-start: b; grid-column-end: b; }
A
B
C
D
E
F

上面,我们将围绕B元素的两条垂直grid line分别命名为b-startb-end,而在引用时,只需指定为b即可。

#b { ... grid-column-start: b; grid-column-end: b; }

在查找时,CSS Grid会自动根据其所在的属性名称,自动为它们加上相应的后缀。

在下面的为grid area命名一节中,我们将会看到,CSS Grid就是根据这个特性添加了隐式的名称。

多条Grid line可以共享同一名称

div.grid-container { display: grid; grid-template-columns: [v-line] 150px [v-line] 1fr [v-line]; } body div.grid-container div { border: 1px dashed gray; } #b { grid-row-start: 1; grid-row-end: 2; grid-column-start: v-line; grid-column-end: v-line; }
A
B
C
D
E
F

上面,在声明时,3条垂直的grid line均使用同一名称v-line,且在引用时也均一样地引用了v-lineCSS Grid会自动地从左到右地提取对应的grid line

如果我们希望改变CSS Grid的这种默认行为,例如,我们希望B元素从第1条grid line开始,至第3条grid line结束,则可以通过在名称后面添加[space] index的方式进行指定。index的值也可为负数。

div.grid-container { display: grid; grid-template-columns: [v-line] 150px [v-line] 1fr [v-line]; } body div.grid-container div { border: 1px dashed gray; } #b { grid-row-start: 1; grid-row-end: 2; grid-column-start: v-line 1; grid-column-end: v-line 3; }
A
B
C
D
E
F

一条Grid line可以有多个名称

同一条grid line可以有多个名称,各个名称之间用空格隔开。引用时,可使用该线条的任一名称。

div.grid-container { display: grid; grid-template-columns: [b] 150px [b a] 1fr [a]; } body div.grid-container div { border: 1px dashed gray; } #b { grid-row-start: 1; grid-row-end: 2; grid-column-start: b; grid-column-end: b; } #a { grid-column-start: a; grid-column-end: a; }
A
B
C
D
E
F

使用span来跨行或跨列

指定单数的起始行列,再指定复数的终止数量

div.grid-container { display: grid; grid-template-columns: 150px 1fr; } #a { grid-column-start: 1; grid-column-end: span 2; } body div.grid-container div { border: 1px dashed gray; }
A
B
C
D
E
F

grid-column-start指定一个起始列,再通过span ngrid-column-end设定一个跨度数值。

上面初始指定的列数为2列,如果指定span n后超过了指定的列数,则会隐式地添加多余的列。例如:

div.grid-container { display: grid; grid-template-columns: 150px 1fr; } #c { grid-column-start: 1; grid-column-end: span 3; } body div.grid-container div { border: 1px dashed gray; }
A
B
C
D
E
F

C元素要求有3列的宽度,则模板列将变为3列。而之前的A, B元素先按默认的2列排列,在变为3列后,其所在行会有留白。

但如果C元素之前的A或B元素已经先排,则A或B元素所在行不会留白。

div.grid-container { display: grid; grid-template-columns: 150px 1fr; } #a { grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 1; } #c { grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: span 3; } body div.grid-container div { border: 1px dashed gray; }
A
B
C
D
E
F

grid-column-start跨列

div.grid-container { display: grid; grid-template-columns: 150px 1fr; } #a { grid-column-start: span 2; } body div.grid-container div { border: 1px dashed gray; }
A
B
C
D
E
F

注意,不能在span的前面再引用一行或一列,例如:grid-column-start: 1 span 2;。这是错误的。

grid-column-end跨列至指定的列名

div.grid-container { display: grid; grid-template-columns: 150px 1fr [v-line]; } #a { grid-column-start: 1; grid-column-end: span v-line; } body div.grid-container div { border: 1px dashed gray; }
A
B
C
D
E
F

注意,grid-column-start不支持这种设置值。

同名的跨域

跨至中间的名称:

div.grid-container { display: grid; grid-template-columns: [a] 150px 1fr [a] 1fr [a]; } #a { grid-column-start: a; grid-column-end: span a; } body div.grid-container div { border: 1px dashed gray; }
A
B
C
D
E
F

跨至最后一个:

div.grid-container { display: grid; grid-template-columns: [a] 150px 1fr [a] 1fr [a]; } #a { grid-column-start: a; grid-column-end: a -1; } body div.grid-container div { border: 1px dashed gray; }
A
B
C
D
E
F

直接指定最后一列就行,不能使用span

小结

使用grid-row-start这些属性名的不便之处在于,在指定grid line时,我们需要脑补这些grid line的序号。此外,细节比较繁杂。

grid-row及grid-column

grid-row指定元素所在的行,grid-column指定元素所在的列。

grid-rowgrid-row-startgrid-row-end的简写。grid-columngrid-column-startgrid-column-end的简写。

基本用法

div.grid-container { display: grid; grid-template-columns: 150px 1fr; } #a { grid-row: 1; grid-column: 2; } #b { grid-row: 3; grid-column: 1; }
A
B
C
D
E
F

上面,我们将A子元素排在第1行第2列,将B子元素排在第3行第1列。代码简洁明了,非常直观。

跨行或跨列

div.grid-container { display: grid; grid-template-columns: 150px 1fr; } #a { grid-row: 1 / span 2; grid-column: 2; } #b { grid-row: 3; grid-column: 1 / span 2; }
A
B
C
D
E
F

对于A元素,使用grid-row: 1 / span 2;来指定所在行从第1行开始,跨2行。对于B元素,使用grid-column: 1 / span 2;来指定所在列从第1列开始,跨2列。

指定起止行列

div.grid-container { display: grid; grid-template-columns: 150px 1fr; } #a { grid-row: 1; grid-column: 2 / 4; }
A
B
C
D
E
F

A元素,使用grid-column: 2 / 4;来指定其单元格从第2条grid-line开始,到第4条grid-line结束。由于模板列只有2列,故会自动添加第3列至模板列中。

注意比较:

grid-column: 1 / span 2: 从第1列开始,跨2列。引用的是grid-cell

grid-column: 1 / 2: 从第1条grid-line开始,至第2条grid-line结束。引用的是grid-line

使用grid-template-areas为空间命名

基本语法

div.grid-container { display: grid; grid-template-areas: "header header header" "nav article aside" "footer footer footer"; grid-template-columns: auto 1fr 20%; gap: 0.2em; } #header { grid-area: header; } #nav { grid-area: nav; } #article { grid-area: article; } #aside { grid-area: aside; } #footer { grid-area: footer; }

Aritcle title

paragraph 1

parapgraph 2

Aside

第一步,先使用grid-template-areas将父容器划分为几大部分,并为它们相应命名。跨行或跨列的,重复上行或上列的名称即可。第二步,将各个子元素通过grid-area与所命名的区域对应起来。

区域名称并无太多限制,甚至可以使用单个英文字符。如果grid-area允许出现留白,则用单个或多个.来代替。

练习:将grid-template-areas的最后一行属性值改为... footer ...看看效果如何。

空间命名的便利之处

空间命名的一个便利之处是非常方便做整个版面的调整。

div.grid-container { display: grid; grid-template-areas: "header header header" "nav article aside" "footer footer footer"; grid-template-columns: auto 1fr 20%; gap: 0.2em; } #header { grid-area: header; } #nav { grid-area: nav; } #article { grid-area: article; } #aside { grid-area: aside; } #footer { grid-area: footer; } @media all and (max-width: 400px) { div.grid-container { grid-template-areas: "header" "article" "footer"; grid-template-columns: 1fr; } #nav, #aside { display: none; } }

Aritcle title

paragraph 1

parapgraph 2

Aside

上面的代码,在桌面电脑上浏览时,效果与上节一致。而当客户端宽度小于等于400px时,此为移动设备的宽度,则只显示主干区域,并将两个非主干区域隐藏起来。

grid-template属性名称

grid-templategrid-template-rows, grid-template-columnsgrid-template-areas的简写。

基本用法

行 / 列的格式来定义。

div.grid-container { display: grid; grid-template: 1fr 1fr 1fr / 150px 1fr; }
A
B
C
D
E
F

定义了3行及2列。其效果等同于:

div.grid-container { display: grid; grid-template-rows: 1fr 1fr 1fr; grid-template-columns: 150px 1fr; grid-template-areas: none; }
A
B
C
D
E
F

融入grid-template-areas

再进一步,将上面的行的定义改写为grid-template-areas的形式:

div.grid-container { display: grid; grid-template: "a a b" 1fr "c c c" 2fr "d e e" 1fr "d f f" 2fr / 50px 100px 50px; } #a {grid-area: a;} #b {grid-area: b;} #c {grid-area: c;} #d {grid-area: d;} #e {grid-area: e;} #f {grid-area: f;}
A
B
C
D
E
F

等同于:

div.grid-container { display: grid; grid-template-areas: "a a b" "c c c" "d e e" "d f f"; grid-template-rows: 1fr 2fr 1fr 2fr; grid-template-columns: 50px 100px 50px; } #a {grid-area: a;} #b {grid-area: b;} #c {grid-area: c;} #d {grid-area: d;} #e {grid-area: e;} #f {grid-area: f;}
A
B
C
D
E
F

相比之下,使用grid-template定义grid areas,然后将各行的高度分别列在各行名称的右边,反倒更为直观。

grid属性名称

gridgrid-template-rows, grid-template-columns, grid-template-areas, grid-auto-rows, grid-auto-columns, 以及grid-auto-flow的简写。

grid-auto-flow属性

grid-auto-flow属性决定先排行还是先排列。

如果属性值是默认值row,则先依据每行中所有列的数量,先排完一行的各列,再排另一行。

div.grid-container { display: grid; grid-template: repeat(4, 1fr) / repeat(2, 1fr); grid-auto-flow: row; }
A
B
C
D
E
F

先排行。每行中有2列,则A, B子元素先排在第1行,然后另起一行,继续排完后面的子元素。

而如果属性值是column,则先依据每列中各行的数量,先排完一列的各行,再排另一列。

div.grid-container { display: grid; grid-template: repeat(4, 1fr) / repeat(2, 1fr); grid-auto-flow: column; }
A
B
C
D
E
F

先排列,每列有4行,故A, B, C, D先排在第1列,然后再另起一列。

grid-auto-flow的属性值还可以是row densecolumn dense。用于决定在前面已经排过的位置中如果出现留白,是否可以由后面的元素排在留白的位置。看下面的例子。

div.grid-container { display: grid; grid-template: repeat(4, 1fr) / repeat(2, 1fr); grid-auto-flow: row; } #a {grid-row-start: 3;} #d {grid-column-start: 2;}
A
B
C
D
E
F

优先按行到排列。我们指定A元素必须排在第3行,因此,BC元素先排在第1行。接着到D元素。由于我们指定该元素必须在第2列,因此它排到了第2行的第2列,导致第2行的第1列留白。E, F元素则跟在A元素之后排列。

现在,将grid-auto-flow的属性值改为row dense

div.grid-container { display: grid; grid-template: repeat(4, 1fr) / repeat(2, 1fr); grid-auto-flow: row dense; } #a {grid-row-start: 3;} #d {grid-column-start: 2;}
A
B
C
D
E
F

与上面不同的是,紧跟在A元素之后的E元素,看到D元素的前面有留白,就在该留白处予以排列。即,后面排列的元素自动填补前面的留白。

再看优先按列排列的情况。

div.grid-container { display: grid; grid-template: repeat(4, 1fr) / repeat(2, 1fr); grid-auto-flow: column; } #a {grid-row-start: 3;} #d {grid-column-start: 2;}
A
B
C
D
E
F

A先排在第1列的第3行,导致该列前面2行留白。到C时,已另起1列至第2列,此时由于D被指定为必须排在第2列,其优先权高于C,因此,先排D再排CEF排在第2列的最后。

grid-auto-flow的属性值改为column dense

div.grid-container { display: grid; grid-template: repeat(4, 1fr) / repeat(2, 1fr); grid-auto-flow: column dense; } #a {grid-row-start: 3;} #d {grid-column-start: 2;}
A
B
C
D
E
F

BC则迫不及待地跳上去填补留白了。

grid属性的用法

div.grid-container { display: grid; grid: 1fr 1fr 1fr / auto-flow 150px 1fr; // 以列为先 }
A
B
C
D
E
F

很多时候,名称越短,功能更丰富,使用更简单,但在CSS中却相反:CSS的属性名称越短,其越难理解与掌握。下面是grid-area简写金字塔:

grid-area
grid-columngrid-row
grid-column-startgrid-column-endgrid-row-startgrid-row-end

下面是grid简写金字塔:

grid
grid-templategrid-auto-columnsgrid-auto-rowsgrid-auto-flow
grid-template-rowsgrid-template-columns

对齐

整个grid在grid container中的对齐

grid container中使用justify-content使整个gridgrid container中水平对齐,在grid container中使用align-content使整个gridgrid container中垂直对齐。

div.grid-container { display: grid; grid-template-columns: 150px 250px; justify-content: center; align-content: center; min-height: 150px; }
A
B
C
D
E
F

上例中灰底部分为grid container所占用的空间,也即grid布局的空间。由于我们指定每行中的2列分别为150px100px,因此不能占满整行,在水平方向上有留白。同样,每行的高度也均使用默认的固定值,3行总高度也不能占满整个grid container的高度上的空间,因此在垂直方向上也有留白。

grid container有留白时,整个grid便可在其中上下左右移动。此时即可在grid container元素中使用justify-contentalign-content来控制grid的偏移位置。

justify-content的值包括:normal, space-between, space-around, space-evenly, stretch, safe, unsafe, center, start, end, flex-start, flex-end, left, right.

align-content的值包括:normal, first, last, baseline, space-between, space-around, space-evenly, stretch, safe, unsafe, center, start, end, flex-start, flex-end.

将上面各个值分别代入justify-contentalign-content,观察实际效果。

在父容器中统一设定子元素应否拉伸

默认情况下,grid container下所有子元素都会自动拉伸以填满grid布局空间。例如:

div.grid-container { display: grid; grid-template-columns: 150px 1fr; min-height: 150px; }
A
B
C
D
E
F

共有2列,第1列占用了150px的grid布局空间,第2列占用该行中grid布局空间的剩余空间。并且,由于grid布局空间最小高度为150px,在高度上的空间也很充裕,每个子元素的高度也被拉伸了。

但,查看每个子元素,其内容均只有一个字母,它们本身的内在尺寸(intrinsic size)实在无法铺满它们所实际占用的grid布局空间。

上面的代码只是确定了每个grid cell的大小。而在空间较大的grid cell内,如果再对子元素进行进一步的布局?

justify-itemsalign-items应运而生。它们均应用于grid container中,用以统一设置所有子元素的属性。前者用于水平方向,后者用于垂直方向。

div.grid-container { display: grid; grid-template-columns: 150px 1fr; min-height: 150px; justify-items: right; align-items: center; }
A
B
C
D
E
F

与上一个例子相比较,各个子元素仍呆在对应的grid cell之内,但每个子元素的边框仅围住了它们的内在尺寸的空间,并且依据我们的代码,在水平方向上靠右对齐,在垂直方向上居中对齐。

justify-items的值包括:normal, stretch, first, last, baseline, safe, unsafe, center, start, end, self-start, self-end, flex-start, flex-end, left, right, center, legacy(默认值)。

align-items的值包括:normal(默认值), stretch, first, last, baseline, safe, unsafe, center, start, end, self-start, self-end, flex-start, flex-end.

将上面各个值分别代入justify-itemsalign-items,观察实际效果。

在子元素中单独设定该元素应否拉伸

上一节,我们在父容器中统一设定了各个子元素应否拉伸,相对应地,justify-selfalign-self可应用在各个子元素上,以设定该子元素不同于父容器统一设定的情况。

div.grid-container { display: grid; grid-template-columns: 150px 1fr; min-height: 150px; justify-items: right; align-items: center; } #a { justify-self: stretch; align-self: stretch; }
A
B
C
D
E
F

在父容器中,我们通过justify-itemsalign-items统一设定了所有的子元素应水平靠右垂直居中对齐,而在A元素中,通过justify-selfalign-self将该元素改为水平及垂直均拉伸。

结语

CSS Grid的编排策略非常灵活,能应对各种各样的需求。然而,健壮的功能往往意味着庞大的身躯,因此,它实际远比从表面看上去时要复杂得多(本文的文本内容加上源代码,超过了2100行,全文历时将近半个月才完成)。

本教程通过丰富的实例,涵盖了CSS Grid最常用的90%以上的内容,希望对掌握并熟练驾驭CSS Grid能起到较大的帮助作用。

参考资源

  1. CSS Grid Layout Module Level 1
  2. CSS Grid Layout Module Level 2
  3. CSS Grid Layout Module Level 3
  4. CSS Box Sizing Module Level 3
  5. CSS Box Alignment Module Level 3
  6. Design From the Inside Out With CSS min-content
  7. MDN fit-content