Skip to content

前面介绍了 flex 布局,flex 布局通常用于比较简单的单行的布局,但面对一些比较复杂的布局,我们就得嵌套多个 flex 布局,这样其实是不合适的,为了实现复杂的布局,CSS 3 提供了 grid 布局,grid 布局远比 flex 布局强大,是 CSS 最强大的布局,也被称作现代网页布局的终极解决方案。

如果说 flex 布局是一维的,是以轴线为基准的,grid 布局就是一种二维布局模式。

grid 本质上就是一个网格,实际上如果读者使用过表格布局,应该会发现表格布局其实很强大,可以轻松实现网页中绝大多数布局结构,但是由于表格布局存在很多性能问题,包括语义化问题,所以直接被 PASS 了,但并不影响它很好用,基于表格布局的思想,形如 bootstrap 这种 UI 框架就提出了栅格布局的概念,这其实就是一种表格布局的思想。而 CSS 3 提出的 grid 布局,也称作网格布局,正是沿用这一思想,所以使用起来相当强大。

但学习之前请读者注意,虽然 grid 布局相当强大,但其并不是 flex 布局的替代品,grid 布局和 flex 布局有各自不同的应用场景,一般情况下,如果是一维布局,例如导航栏这种,使用 flex 布局会非常方便,而对于页面级的复杂布局,使用 grid 则更为方便。

css
.grid-box {
	display: grid;
}

当我们给盒子设置 display:grid 之后,该盒子内部就使用了 grid 布局,和 flex 类似,也可以设置 display:inline-grid,当设置为 inline-grid 的时候,容器本身的行为就类似行块盒,而 grid 则类似块盒的行为。

html
<!DOCTYPE html>
<html lang="cmn-hans">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .grid-box {
            display: grid;
        }

        .item {
            height: 100px;
            width: 100px;
            background-color: red;
        }
    </style>
</head>
<body>
    <div class="grid-box">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
    </div>
</body>
</html>

我们有了如上代码,查看浏览器:

![Pasted image 20240507060039](/notes-assets/04.CSS3/assets/Pasted image 20240507060039.png)

我们发现布局好像没什么变化,都是每个 item换行展示,但注意到 grid-box 后面有一个 grid 按钮,点击后会看到元素的布局,这里就可以很明显地看到这玩意儿是一个 5 行 1 列的网格。

也就是说默认情况下,网格是一列的,我们可以使用 grid-template-columns 属性来确定列。

css
.grid-box {
	display: grid;
	grid-template-columns: 200px 150px 200px;
}

![Pasted image 20240507061349](/notes-assets/04.CSS3/assets/Pasted image 20240507061349.png)

此时这样设置后,相当于设置了 3 列,第一列 200 像素,第二列 150 像素,第三列 200 像素。

我们用同样的方式,使用 grid-template-rows 可以设置行数

css
.grid-box {
	display: grid;
	grid-template-columns: 200px 150px 200px;
	grid-template-rows: 200px 200px;
}

![Pasted image 20240507061525](/notes-assets/04.CSS3/assets/Pasted image 20240507061525.png)

这里我设置了两行,每一行都是 200 像素,可以在页面控制台中很明显地看到其结构。

实际上,通过 grid-template-rows 和 grid-template-columns 可以将容器划分为一个一个小格子,而该容器的一个个子元素默认情况下就会被依次填入这些小格子当中,也就形成了上面的这些效果。

如果这两个属性填入百分比,则是容器的百分比

当然,这些我们都可以很灵活的配置

html
<!DOCTYPE html>
<html lang="cmn-hans">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .grid-box {
            display: grid;
            grid-template-columns: 200px 150px 200px;
            grid-template-rows: 200px 200px;
        }

        .item {
            height: 100px;
            width: 100px;
            background-color: red;
        }
    </style>
</head>

<body>
    <div class="grid-box">
        <div class="item"></div>
        <div class="item"></div>
    </div>
</body>
</html>

![Pasted image 20240507063444](/notes-assets/04.CSS3/assets/Pasted image 20240507063444.png)

grid-template-columns 和 grid-template-rows 也可以填入一些其他的值,例如 fr 单位,如果填入 fr 单位,会按比例将容器剩余宽度进行等分。

css
.grid-box {
	display: grid;
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 200px 200px;
}

![Pasted image 20240507074906](/notes-assets/04.CSS3/assets/Pasted image 20240507074906.png)

可以看到,此时三列都是 1 fr,所以这就是一个三等分,注意,分割的是剩余空间。

由于元素并没有默认高度,取决于元素内容,所以在不指定高度的情况下设置 grid-template-rows 为 fr 单位是无效的

该属性可以填入的数值还非常多,比如可以填入 auto, min-content, max-content 这几个关键字,auto 也就是自动计算,min-content 即设置为内容的最小值,也就是内容可以换行展示的最小宽度,max-content 则为内容的最大值,也就是让内容不换行展示,让内容的宽度最大,这两个属性都是根据内容自动撑开,而 auto 的计算则比较复杂,这里不涉及。

除了这几个关键字之外,还有一些可以使用的函数:

  • repeat():该方法接受两个参数,第一个参数为次数或者一些关键字,第二个参数需要重复内容,该函数只能在 grid-template-rows 和 grid-template-columns 使用。
    • 第一个参数的关键字:
      • auto-fit:按照指定尺寸尽可能去分完所有剩余空间。
      • auto-fill:和 auto-fit 类似,区别在于 auto-fit 未被 DOM 元素占用的格子将会收缩到一起,而 auto-fill 则会直接创建新的格子,哪怕这个格子没有被 DOM 元素占用。这里说的被 DOM 元素占用,就是指一个 DOM 元素默认会被放到一个格子中去。
  • minmax():该方法接受两个参数,第一个参数为最小值,第二个参数为最大值,需要带上单位,此时如果宽度发生变化,就可以按照这个参数的最小值和最大值进行动态变化,例如 minmax(50px,1fr),那么此时就可以保证该网格的宽度为 1 fr,并且最低不低于 50 像素。
  • fit-content():该方法可以限制网格的尺寸的宽度,这个宽度必须在 min-content 和 max-content 之间,如果小于 min-content 则使用 min-content,如果大于 max-content 则使用 max-content

这些函数其实是可以嵌套组合的,例如可以在 repeat 函数中嵌套 minmax 函数。

网格线是可以取名的,如下所示:

css
.grid-box {
	display: grid;
	grid-template-columns: [test] 1fr [test2] 1fr [test3] 1fr [test4];
	grid-template-rows: 200px 200px;
}

以上的 [test] 这种加了中括号的位置,位于两个区域之间,实际上就是在给网格线命名

除了以上两个之外,还有一个 grid-template-areas 属性,该属性可以用来给单元格命名,命名方式比较神奇。

css
.grid-box {
    display: grid;
    height: 100vh;
    grid-template-columns: 1fr 5fr 1fr;
    grid-template-rows: repeat(3, 1fr);
    grid-template-areas:
        "header header header"
        "sideBar main sideBar"
        ". fotter ."
    ;
}

可能改属性和我们学过的所有属性都不一样,首先,grid-template-rows 和 grid-template-columns 将网格划分为了 3 行 3 列,相当于有九个格子,在 grid-template-areas 中就可以对这九个格子命名,这个属性可以传入多个参数,每个参数都是一个字符串,一个字符串就代表了一行,比如我这里的格子是三行,就写上三个字符串,每一行中有很多小格子,直接在字符串中写名字即可,小格子的名字使用空格进行分割,例如在上面的例子中,第一行的所有格子都叫 header,第二行左右两个格子为 sideBar,中间为 main,最后一行中间的格子为 fotter,左右两个格子这里写的是 .,这个 . 表示留空,也就是没有名字。这个名字可以是中文,只要确保格子之间的名字使用空格分割好即可。

读者只需要知道,不管是分割线还是单元格都是可以命名的,至于这个命名有什么用,后面再进行介绍。

grid-template-areas, grid-template-rows,grid-template-columns 可以合成为一个 grid-template 的复合属性,我们可以使用 grid-template:行/列 的方式来同时设置 grid-template-rows 和 grid-template-columns ,如果要将 grid-template-areas 也加进去,就会稍显麻烦,这里也进行介绍:

css
grid-template:
		"header header header" 1fr
		"aside-1 main aside-2" 5fr
		". footer ." 1fr 
		/ 1fr 5fr 1fr;
}

首先先写行,先用字符串写出行的名字,然后写出行高,逐个写出所有行,然后使用 / 进行分割,挨个写出列宽即可。

注意,repeat 函数只能用于 grid-template-columns 和 grid-template-rows 以及 grid-template 传入两个值的情况,在上面这种情况下是不生效的。

在默认情况下,当我们规定了一个 n*m 的网格,那么当我们在网格中添加元素的时候,会从左到右从上到下一个一个放到网格中去,也就是说最多能容纳 n*m 个元素,但是如果我们的子元素超过了这个数字呢?也就是超过了我们规定网格的最大数量,这个时候就会自动向下创建网格把它放进去,我们称其为隐式网格,我们可以通过设置 grid-auto-rows 和 grid-auto-columns 来规定这种自动生成的隐式网格的尺寸,一般只用 grid-auto-rows,因为列宽一般都是固定写好的,所以我们设置 grid-auto-columns 很多时候也不会生效。

在 flex 布局中,我们学习了 flex-direction 属性,可以用来设置主轴的方向,而 flex-warp 我们可以认为是在设置交叉轴的方向,在 grid 布局中,网格也是有方向的,一般情况下是从上往下的,我们也可以让它横过来,也就是从左往右,就需要通过 grid-auto-flow 设置即可。什么叫网格方向呢?我们知道,默认情况下,网格布局中的元素会按照从上往下从左往右的方式放进一个一个的网格中,而 grid-auto-flow 改变的就是这种排列方式,改属性默认值为 row,也就是按照行来排列,当我们将其设置为 column 的时候,就会按照列来排列,此时就是一列一列进行排列,每一列从上往下。

除了 column 和 row 两个值以外,grid-auto-flow 还支持一个 dense 的参数,如果只传入一个 dense,相当于 row dense,也就是按照行稠密排列,如果写上 column dense,就是按照列稠密排列。实际上,对于单一的元素,我们是可以指定其起始位置的,比如我让第一个元素的起始列为第二条线,那么第一个格子默认情况下就会空出来,此时如果加上了 dense,那么第二个元素将不会排列在第一个元素后面,而是会去填满第一个元素前面的空格。

至此我们学习了以下几个属性

  • grid-template-rows
  • grid-template-column
  • grid-template-areas
  • grid-template
  • grid-auto-columns
  • grid-auto-rows
  • grid-auto-flow

以上其实是六个属性,因为 grid-template 其实是复合属性,这六个属性可以使用 grid 一个属性来简写,如果要用 grid 一个属性来简写,首先写 grid-template 上的属性,同时也可以写 auto-flow

这个 grid 属性把所有属性都混在一起了,太过麻烦,所以我们不用,不如分开写,把 grid-template 掌握好即可

在 grid 布局中有一个 gap 属性,用于描述格子与格子之间的距离

css
.grid-box {
	display: grid;
	height: 100vh;
	grid-template: repeat(3, 50px) / repeat(3, 50px);
	gap: 20px;
}

![Pasted image 20240507181452](/notes-assets/04.CSS3/assets/Pasted image 20240507181452.png)

可以看到,此时格子与格子之间的间隙就变成了 20 像素,但边界上的格子并不会和边界产生间隙,这个 gap 也是 row-gap 和 col-gap 的复合属性,并且在 flex 布局中其实也可以使用 gap 属性。

以上介绍的基本就是如何使用 grid 容器去画出一个一个格子,接下来就考虑一下格子是怎么排列的,我们先回顾一下 flex 的排列方式有哪些属性,首先明确这么几个东西,justify 表示主轴,align 表示侧轴,items 表示每个项目应该怎么排列,content 表示多个项目之间应该怎么排列。基于此,flex 的排列方式就有以下三个属性

  • justify-content:主轴上的排列方式。
  • align-items:侧轴如果只有单个元素,应该如何排列,或者说如果只有一行应该如何排列。
  • align-content:侧轴上如果有多个元素,应该如何排列,也就是说如果有多行应该如何排列。

很显然,这里并没有 justify-items,这是由于 flex 布局中这三个属性已经足够满足需求,不需要再引入 justify-items 属性,但这个属性在 grid 布局中却真实存在。

在 grid 布局中也很类似,但 grid 布局中,我们可以认为主轴永远是自左向右的,交叉轴永远是至上到下的。

  • justify-items:网格内部盒子的水平方向如何排列,start 则表示从格子起始边缘开始,end 表示从结束边缘开始,center 表示居中。默认值为 stretch,也就是撑满。
  • align-items:网格内部盒子的垂直方向如何排列,取值同 justify-content,这个属性还有一个 baseline 属性,为文字基线对其,了解即可。此属性默认值为 normal,如果是一个普通元素,展示的效果就是 stretch,但是如果是一个带有特定比例的元素,例如图片,那么展示的效果就是 start。
  • place-items:为 align-items 和 justify-items的简写属性,第一个参数设置 align-items,第二个参数设置 justify-items。
  • justify-content:网格在容器中的横向排列方式,属性取值同 flex 类似
  • align-content:网格在容器中的纵向排列方式,属性取值同 flex 类似
  • place-content:为 justify-content 和 align-content 的复合属性。

注意网格和容器的区别

css
.grid-box {
 display: grid;
  width: 500 px;
 height: 500 px;
grid-template: repeat (2, 50 px) / repeat (2, 50 px);
border: 1 px solid black;
  }

![Pasted image 20240507191428](/notes-assets/04.CSS3/assets/Pasted image 20240507191428.png)
外层这个是容器,容器内部的才是网格。从这也可以看出,前提是网格需要在容器内有剩余空间,但如果我们使用 fr 单位,那么网格将会自动撑满所有剩余空间,也就没有多余空间用来对其了,所以在网格布局中,实际上 justify-content 和 align-content 用的并不是很多。

如果读者有点悟性,可以认为 flex 如果不设置 flex-wrap,就是单行的 grid 布局,如果设置了 flex-wrap,实际上 flex 也是可以实现类似网格布局的效果的,但是实现起来就显得太麻烦了。

以上介绍完了 grid 的所有容器属性,现在我们应该可以划出任何样子的网格了,不管是宽高如何自定义,网格见的间隙如何,网格如何排列,这些现在都不是什么问题,既然容器相关的我们都能了解了,接下来就是项目属性了。

默认情况下,一个 grid 项目,也就是一个 grid 容器的子元素,是占用一个网格的,当我们给 grid 容器添加子元素,他会将这些 grid 项目一个一个放到容器中的网格中去。那如果我要让一个项目占用好几个网格,这里我们给项目使用 grid-column-start,grid-column-end,grid-row-start,grid-row-end 这四个属性即可,就可以表示四条边,这四条边就可以围出一个矩形,该项目就会放到这个矩形的位置上去。

css
.grid-box {
	display: grid;
	grid-template: repeat(3, 100px) / repeat(3, 100px);
}

.grid-box>.item {
	grid-row-start: 3;
	grid-row-end: 4;
	grid-column-start: 2;
	grid-column-end: 4;
	background-color: aqua;
}

![Pasted image 20240507193128](/notes-assets/04.CSS3/assets/Pasted image 20240507193128.png)

可以看到,此时这个项目就被放到了指定的网格上去,此时该项目就占用两个网格。

读者应该还记得,前面我们提到过,这些网格线是可以取名的,在这四个属性中,就可以应用网格线的名称,如下所示:

css
.grid-box {
	display: grid;
	grid-template: 100px [test1] 100px 100px [test2] / 100px 100px [test3] 100px [test4];
	/*取名的时候,可以使用 [name1 name2],这种方式给同一条线起多条名*/
	/*实际上取名还有一些更牛逼的用法*/
	/*100px [A-start] 100px [A-end B-start] 100px [B-end]  / [C-start] 100px 100px [C-end] 100px;*/
}

.grid-box>.item {
	/*在这里可以直接写A,如果写在start中,会自动变成A-start,如果写在end中,会自动变成A-end*/
	grid-row-start: test1;
	grid-row-end: test2;
	grid-column-start: test3;
	grid-column-end: test4;
	background-color: aqua;
}

^824b35

但是一般情况下,我们一般不给网格线取名,我们用网格线的编号已经足够满足要求了,给网格线取名唯一的好处就是语义化很不错。

如果读者细心,可以在浏览器控制台中看到负数的线,-1 表示的就是倒数第一条线,也是可以作为网格线编号来使用的。

除了可以填入网格线的编号和名字之外,还可以填入 span 关键字,如果在 start 中填入 span n,则 start 的数值为 end - n,如果在 end 中填入 span n,则 end 的数值为 start + n,我们可以认为 span 就是扩充的意思,如果在 end 中写 span 2,表示基于起始网格线扩充两个网格线,如果在 start 中填入 span 2 表示从结束网格线开始向前找两个线段。

css
.grid-box>.item {
	grid-row-start: 1;
	grid-row-end: span 2;
	grid-column-start: 1;
	grid-column-end: span 2;
	background-color: aqua;
}

![Pasted image 20240507195143](/notes-assets/04.CSS3/assets/Pasted image 20240507195143.png)

grid-row-start 和grid-row-end 可以用 grid-row 属性来简写,语法为 grid-row:start/end,同样 grid-column-start 和 grid-column-end 可以用 grid-column 属性来简写,语法为 grid-column:start/end ,除此之外,还有一个更牛逼的缩写,就是 grid-area,该属性的语法为 grid-area:row-start/column-start/row-end/column-end。

读者应该还记得,我们前面提到的给一个区域命名,也就是 grid-template-areas 中,给空间的命名,如果给一个矩形区域取为同一个名之后,就可以直接 grid-area:区域名 即可。其本质也是通过线来合围的,当我们给一个矩形区域命名之后,这个矩形区域的上边,下边,左边,右边,都会分别命名为 区域名-start,区域名-end,此时只需要将 grid-area 设置为 区域名,相当于 grid-area:区域名/区域名/区域名/区域名,结合 11.grid 这里的代码自行分析其原理。

除了以上属性之外,grid 中的项目还支持 justify-self 和 align-self,这个属性相信不需要再过多介绍,也就是自身的对齐方式。

以笔记为镜,可以知得失