
我疏忽了,忘记把这几章节放在布局那边了,不过后来想想还是独立成章更好一点,所以在进阶样式中间插入几个章节,叫做网格布局。
网格布局,即 grid 布局,虽然有不少人都说它是“最强大”的布局方案,但我认为没有哪一种一定比另一种强的说法,只有在什么时候适合用什么布局,目前如果真的是从零开始了解 CSS 的话,我还是觉得弹性盒子 display: flex; 这种更加直观,所以优先介绍了——正好自己重新学一次挺好的。
不过,不可否认的是,网格布局的确在很多时候是优于弹性盒子布局的,因为:
- flex 布局是轴线布局,只能指定"项目"针对轴线的位置,可以看作是一维布局
- grid 布局则是将容器划分成"行"和"列",产生单元格,然后指定"项目所在"的单元格,可以看作是二维布局
所以接下来就看看网格布局到底有多强大(很多内容参考自阮一峰老师的博客:CSS Grid 网格布局教程,如果有兴趣可以看看原文)。
1 概念
CSS 3 引入网格布局,是一次大革新,它把网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局。以前,只能通过复杂的 CSS 框架达到的效果,现在浏览器内置了,如下例(非常简单):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello</title>
<style>
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: 33% 33% 33%;
grid-template-rows: 33% 33% 33%;
}
.item {
border: thin solid black;
text-align: center;
font-size: 64px;
color: white;
line-height: 200px;
}
#div1 {
background: red;
}
#div2 {
background: orange;
}
#div3 {
background: yellow;
}
#div4 {
background: green;
}
#div5 {
background: blue;
}
#div6 {
background: aqua;
}
#div7 {
background: purple;
}
#div8 {
background: brown;
}
#div9 {
background: grey;
}
</style>
</head>
<body>
<div class="container">
<div class="item" id="div1">1</div>
<div class="item" id="div2">2</div>
<div class="item" id="div3">3</div>
<div class="item" id="div4">4</div>
<div class="item" id="div5">5</div>
<div class="item" id="div6">6</div>
<div class="item" id="div7">7</div>
<div class="item" id="div8">8</div>
<div class="item" id="div9">9</div>
</div>
</body>
</html>

接下来我们可以来看看网格布局的一些基础概念先:
1.1 容器和项目
采用网格布局的区域,称为容器(container),容器内部采用网格定位的子元素,称为项目(item),比如上面例子里面的 div:
<div class="container">
<div class="item" id="div1">1</div>
<div class="item" id="div2">2</div>
<div class="item" id="div3">3</div>
<div class="item" id="div4">4</div>
<div class="item" id="div5">5</div>
<div class="item" id="div6">6</div>
<div class="item" id="div7">7</div>
<div class="item" id="div8">8</div>
<div class="item" id="div9">9</div>
</div>
最外层的 div.container 就是容器,而内层所有的 div.item 就是项目了;留意,项目必须是容器内的第一层子元素,不能再嵌套,比如里面还有个 <p> 标签,就不能再作为项目了,因为网格无法识别。
1.2 行和列
容器里面的水平区域称为行(row),垂直区域称为列(column),如下图:

这个应该比简略图直观一点,如果不好懂,等你们反馈哦。
1.3 单元格
其实就是行列相交的位置,一个个小格子(和Excel里面的单元格是一个意思),也就是 cell;比如上图中,有9个单元格,因为它是由3行和3列构成的,也就是3 * 3 = 9。
1.4 网格线
划分网格的线,称为网格线(grid line),两条水平网格线之间就是行,两条垂直网格线之间就是列。
正常情况下,n 行有 n + 1 根水平网格线,m 列有 m + 1 根垂直网格线,比如三行就有四根水平网格线:

垂直网格线也是一样的,就不单独截图了。
2 网格容器
为了使用网格布局,首先要定义网格容器,我们可以直接在容器上使用 display 属性来声明一个容器,其格式如下:
display: grid | inline-grid | subgrid
- grid:定义一个块级的网格容器
- inline-grid:定义一个内联的网格容器
- subgrid:定义一个继承其父级网格容器的行和列的大小的网格容器,它是其父级网格容器的一个子项,不同目前仅有 Firefox 支持,所以目前不建议使用
其中我们最常用的还是 grid,除非有特殊原因,很少使用 inline-grid,所以接下来就主要针对 grid 这个值来继续介绍。
特别要注意一点:设为网格布局以后,容器子元素(项目)的float、display: inline-block、display: table-cell、vertical-align 和 column-* 等设置都将失效。
2.1 定义容器的行列
容器指定了网格布局以后,接着就要划分行和列,grid-template-columns 属性定义每一列的列宽,grid-template-rows 属性定义每一行的行高,比如最开始的例子:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: 33% 33% 33%; /* 列宽 */
grid-template-rows: 33% 33% 33%; /* 行高 */
}
上面的代码,指定了3列,每列宽是总宽度 600px 的 33%;同时指定了3行,每行高是总高度 600px 的 33%。
当然,也可以直接使用具体的数值,比如我们明确的知道 600px 的 1/3 是 200px,这里也可以写成:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: 200px 200px 200px; /* 列宽 */
grid-template-rows: 200px 200px 200px; /* 行高 */
}
上面这样的写法,肯定会有人说,只有3行3列这样写还好,如果是9列10多行怎么办?不用担心,CSS 3 没有那么傻的。
(1)重复函数
其实 CSS 3 也提供了一个 repeat() 函数,上面的代码可以写成这样:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: repeat(3, 200px); /* 列宽 */
grid-template-rows: repeat(3, 200px); /* 行高 */
}
repeat() 可以接受两个参数,前面一个是重复次数,后面一个是重复的内容,也就是说,如果我们这样写,也可以:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: repeat(2, 10% 20%);
grid-template-rows: repeat(3, 200px);
}
网格列,将按照每列2个不同宽度,重复2次,而行将保持不变,结果如下:

(2)自动填充
有时,单元格的大小是固定的,但是容器的大小不确定。如果希望每一行(或每一列)容纳尽可能多的单元格,这时可以使用 auto-fill 关键字表示自动填充:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: repeat(auto-fill, 100px);
}

这里可以注意到,由于没有设置行高,所以自动填充成两行之后,行高变成了容器总高度的1/2。
(3)比例填充
为了方便表示比例关系,网格布局提供了 fr 关键字(fraction 的缩写,意为"片段")。如果两列的宽度分别为 1fr 和 2fr,就表示后者是前者的两倍:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: 1fr 2fr;
}

fr 关键字可以和长度数值结合起来使用,这样更加灵活和方便,比如:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: 100px 1fr 2fr;
}

这里比较有意思,可以算一下:原本是 600px,第一列设置为 100px,则第二列是 (600px - 100px) / 3 ≈ 166.7px,第三列则是第二列的两倍。
(4)长度范围
minmax() 函数产生一个长度范围,表示长度就在这个范围之中。它接受两个参数,分别为最小值和最大值:
grid-template-columns: minmax(0, 2fr) minmax(0, 2fr) minmax(0, 1fr);

(5)自动长度
auto 关键字表示由浏览器自己决定长度,我个人喜欢把它用在 minmax() 里面:
grid-template-columns: minmax(20px, auto) 1fr 1fr;

如果使用 auto 和固定长度单位来设置的话,则该列(行)的长度会自动调整为最大长度,比如:
grid-template-columns: auto 150px 150px;

此时,auto 也就是 300px (= 600px - 150px - 150px)。
(6)网格线的名称
grid-template-columns 属性和 grid-template-rows 属性里面,还可以使用方括号,指定每一根网格线的名字,方便以后的引用(这个我还真的没用过):
grid-template-columns: [c1] auto [c2] 150px [c3] 150px [c4];
grid-template-rows: [r1] 200px [r2] 200px [r3] 200px [r4];
上面代码指定网格布局为3行3列,因此有4根垂直网格线和4根水平网格线。方括号里面依次是这八根线的名字。
网格布局允许同一根线有多个名字,比如[fifth-line row-5] 。
通过上面的各个例子,其实我们可以发现,如果仅仅是需要做网页布局的话,grid-template-columns 就已经非常方便了,比如 SharePoint 常用3栏式布局,其样式声明只需要两行 CSS 代码就可以实现了:
#container {
display: grid;
grid-template-columns: 15% 80% 5%;
}
2.2 定义容器区域
grid-template-areas 属性用于定义区域,一个区域由单个或多个单元格组成,比如:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: 33% 33% 33%;
grid-template-rows: 33% 33% 33%;
grid-template-areas: 'a b c'
'd e f'
'g h i';
}
上面代码先划分出9个单元格,然后将其定名为 a 到 i 的九个区域,分别对应这九个单元格,当然还可以合并,只要把命名写成一样的就可以了,如下:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: 33% 33% 33%;
grid-template-rows: 33% 33% 33%;
grid-template-areas: 'a a a'
'b b c'
'd d d';
}
这样的写法,则最上面一行全都合并为区域 a,中间的前两格合并为区域 b,下面一行合并为区域 d。
如果有区域是不需要使用的,可以通过 . 来标记:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: repeat(4, 25%);
grid-template-rows: repeat(3, 33.3%);
grid-template-areas: 'a a . b'
'c c . b'
'd d . e';
}
注意,区域的命名会影响到网格线。每个区域的起始网格线,会自动命名为区域名-start,终止网格线自动命名为区域名-end。
比如,区域名为 header,则起始位置的水平网格线和垂直网格线叫做 header-start,终止位置的水平网格线和垂直网格线叫做 header-end。
不过单独定义容器的区域还没有将定义过程完整走下来,接下来还需要定义项目的区域名,之后在介绍项目属性的时候再完善整个流程。
2.3 定义行列间距
默认的网格,项目之间是没有间距的,为了让项目看起来更分明,我们可以通过定义行列的间距,来分割开各个项目。
CSS 3 提供了三个和网格间距有关的属性:
- row-gap:定义行间距
- column-gap:定义列间距
- gap:简写属性,定义行列间距
下面是个例子:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: repeat(3, 33%);
grid-template-rows: repeat(3, 33%);
row-gap: 10px;
column-gap: 10px;
}

如果使用 gap 则更简单,可以改写成如下代码:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: repeat(3, 33%);
grid-template-rows: repeat(3, 33%);
gap: 10px 10px;
}
如果 gap 省略了第二个值,浏览器认为第二个值等于第一个值。
这里要插一句,因为最新标准改成了 gap,其实之前是 grid-gap,也就是说新标准把 grid- 这部分给去除了,所以看到老代码的时候,出现了 grid-row-gap 之类的别奇怪,其实就是现在的 row-gap。
2.4 项目摆放顺序
划分网格以后,容器的子元素会按照顺序,自动放置在每一个网格。默认的放置顺序是“先行后列”,即先填满第一行,再开始放入第二行,即之前例图的顺序,可以再看一次:

我们可以通过 grid-auto-flow 这个属性来控制项目的摆放顺序,比如先列后行:
grid-auto-flow: column;

grid-auto-flow 可以接受如下值:
- row:默认值,先行后列
- column:先列后行
- dense:按照先后顺序排列,一般与 row 或者 column 同时设置,因为它本身不存在方向性
前面两个都比较好理解,dense 稍稍不好理解,我们可以参考下面的例子:
(1)当1和2号项目都占了两个单元格的时候,默认的 grid-auto-flow: row; 会产生下图这样子:

当我们使用 grid-auto-flow: row dense; 的时候,项目1后面的空白单元格就会被项目3填充:
.container {
width: 600px;
height: 600px;
display: grid;
grid-template-columns: repeat(3, 33%);
grid-template-rows: repeat(3, 33%);
gap: 10px 10px;
grid-auto-flow: row dense;
}

(2)如果我们使用 grid-auto-flow: column; 并且项目1和2占据了上下两个单元格的时候,项目1下面会出现空白单元格:

而如果我们使用 grid-auto-flow: column dense; 的时候,则项目1下面的空白的单元格会被项目3填充:
grid-auto-flow: column dense;
