CSS Grid网格布局全攻略

11,612 阅读18分钟

所有奇技淫巧都只在方寸之间。

几乎从我们踏入前端开发这个领域开始,就不停地接触不同的布局技术。从常见的浮动到表格布局,再到如今大行其道的flex布局,css布局技术一直在不断地推陈出新。其中网格布局(grid)作为css3的产物,它更加贴近网页设计师所使用的布局策略,学习并利用好它可以让我们免受很多布局困扰。

虽然网格布局好处有很多,但学习起来并不简单,原因是用来设置布局的属性实在太多,其中光是作用于父容器的属性就有17种,再加上子元素属性有10种,另外还有这些属性值的不同取值方式。这些对于记忆来说绝对是个不小的负担。那么这么多属性以及用法,要如何在短时间内消化掉呢?在接下来这篇文章里,我将针对这27种属性以及它们各自的用法,分享我独家的学习策略,希望对大家的学习有所帮助。

布局之道

CSS作为一种网页排版设计语言,其核心的设计思想必然要遵守相关的领域知识。网格布局是一种二维布局结构,它是由纵横相交的两组网格线形成的框架性布局结构。网页设计者可以利用这些由行(row)和列(column)形成的框架性结构来布局设计元素。 在定义一种网格布局结构的时候,我们需要在父容器上描述要布局的主体框架结构。为了描述这一框架结构,我们就需要给它的基本构成元素命名。一个网格布局的构成元素可以概括为以下几种概念:

  • row line: 行线
  • column line: 列线
  • track: 网格轨道,即行线和行线,或列线和列线之间所形成的区域,用来摆放子元素
  • gap: 网格间距,行线和行线,或列线和列线之间所形成的不可利用的区域,用来分隔元素
  • cell: 网格单元格,由行线和列线所分隔出来的区域,用来摆放子元素
  • area: 网格区域,由单个或多个网格单元格组成,用来摆放子元素
    grid基本概念

牢记上述这些概念是之后熟练掌握和应用网格布局的基础。

构建之法

要熟练掌握一门技术,核心是找到最基本的套路,然后不断练习从而可以在之后的实践过程中减少决策的时间。所以,这一部分主要就是介绍网格布局构建过程中的一些常用套路。 这里我们要解决的问题是,如何利用最基本的规则来构建出理想的布局模型。在布局过程中,归根结底需要处理的就两种页面元素:父容器和子元素。前者主要用来设置基础的布局框架,相当于建筑中的设计蓝图,而后者就是用来进行个性化的布局调整。因此我个人归纳了在使用网格布局过程中的套路是:针对父容器元素进行设置需要三个步骤:定框架、设间隔和找对齐,对子元素来说有两个步骤:摆位置和找对齐。我把它们统称为**"32构建之法"**。

在这一小节中,我将把重心主要放在网格布局中所有用到的27个属性名的讲解上,而取值逻辑将在最后一部分进行统一介绍。

父容器

定框架

设置父容器的网格布局的第一步就是将父容器的盒模型设置为grid,这样就能触发渲染引擎的网格布局算法。

.parent {
	display: grid;
}

接着我们要开始准备**"画线"**,即设置所需行和列的基础线。这些线条将构成我们接下来进行布局排布的基础模板(template)。在画线过程中,我们需要分别根据行(row)和列(column)两个维度进行设置。你需要画几条线,就设置几个值(不包括边框),其取值是轨道(track)的大小。这里我先画出一个3x3的网格框架,代码如下:

.parent {
	display: grid;
	grid-template-rows: 100px 100px 100px;
	grid-template-columns: 100px 100px 100px;
}

在这里你也可以选择使用缩写形式同时为行和列设置值,采用/分隔开:

.parent {
	display: grid;
	grid-template: 100px 100px 100px / 100px 100px 100px;
}

image.png

画完线后,下一步我们可以选择为这些线条和线条之间形成的网格区域(area)进行命名,这样在后续使用的时候就能直接使用这些名字,便于子元素的定位。

.parent {
	display: grid;
	grid-template-areas: "a a b"
	                     "c d e"
	                     "c d ."
}

上面这两步画线和命名同样可以采用缩写形式进行设置,代码如下:

.parent {
  	display: grid;
  	grid-template: "a a b" 101px
  	               "c d e" 102px
  	               "c d ." 103px / 104px 105px 105px
}

因为使用grid-template同时设置行列和区域名的写法比较复杂,为了讲解方便,我把值设置成规律的递增数字。其中(101, 102,103)设置的是grid-template-rows的值,而(104,105,106)设置的则是grid-template-columns的值。

image.png

到这一步,我们可以说已经完成所需工作的一大半了。

设间隔

间距(gap)的设置在实际开发中是可选的,主要根据网页设计的需求而定。如果你需要给网格线之间设置间距,我们可以在行列两个维度上分别进行设置, 下面这段代码将给每个行和列分别设置10px的间隔:

.parent {
	display: grid;
	grid-template: 100px 100px 100px / 100px 100px 100px;
	grid-row-gap: 10px;
	grid-column-gap: 10px;
}

如果采用缩写形式,上述代码又可以简化成:

.parent {
	display: grid;
	grid-template: 100px 100px 100px / 100px 100px 100px;
	grid-gap: 10px 10px;
}

设置后的效果如下:

找对齐

有了前面两个步骤,我们的网格布局框架基本上算是搭建得差不多了。每个子元素都会默认占据一个网格区域。而在父容器这里我们如果有需要,就要进行最后一个步骤:找对齐。所谓对齐方式,可以分为内部和外部两种(前者是针对每个网格区域的子元素而言,而后者是相对于网格区域本身)。另外在行和列(更专业的术语是main axis和cross axis)上又各自有两个维度,这就构成了4种设置对齐的方式。

先来处理一下每个子元素相对网格区域内部的对齐方式:

.parent {
	display: grid;
	grid-template: 100px 100px 100px / 100px 100px 100px;
	grid-gap: 10px 10px;
	justify-items: center;
	align-items: center;
}

在上面的代码中我分别在行和列方向上都设置了居中对齐,这样每个网格区域中的子元素相对于各自的区域行为是一致的,都能均匀排布。可以看到效果如下图所示:

image.png

再来看一下另一种情况:

.parent {
	display: grid;
	width: 500px;
	height: 500px;
	grid-template: 100px 100px 100px / 100px 100px 100px;
	grid-gap: 10px 10px;
	justify-content: space-between;
	align-content: center;
}

有时候我们设置的网格不足以覆盖整个父容器的大小时,比如在上述的例子中整个父容器有500px*500px的大小,而我们只设置了300px*300px的网格区域,这时候就需要指定多出来空间的处理规则。justify-contentalign-content就是分别在行和列两个方向上用来解决这个问题的属性。它们将针对每个网格区域去设置其在父容器中的对齐方式。

image.png

justifyalign这两个单词在方向上比较容易搞混,所以我在记忆上采取的方式是记住justifyrowaligncolumn这两个合并词,它们长度差不多。如果你有更好的记忆方式,请留言告诉我。

子元素

我们通过在父容器上搭建好了基础的框架后,对于大部分子元素来说,就已经能够很好地满足布局要求了。针对部分子元素,可以根据需求进行微调。如果要在子元素上进行布局微调,通常需要以下两个步骤:摆位置和找对齐

摆位置

像下棋一样,针对子元素的排布,我们需要给它们指定要摆放的具体位置。要确定具体位置,可以利用之前在父容器中所指定的线名区域名来定位。一种方式是直接通过设置起始行,结束行和起始列,结束列来给子元素划定它所要摆放的区域,另外一种方式是指定要摆放的区域名。

/* 指定起始行,结束行,起始列,结束列 */
.child:first-child {
    grid-row-start: 1;
    grid-row-end: 2;
    grid-column-start: 1;
    grid-column-end: 3;
    background: red;
}

/* 使用缩写形式 */
.child:nth-child(2) {
	grid-row: 2/3;
	grid-column: 2/4;
	background: yellow;
}

/* 直接指定区域名 */
.child:nth-child(3) {
	grid-area: i;
	background: green;
}

这段代码的效果如下:

image.png

还有一种更加灵活的设置位置方式,是指定跨越的行数和列数,关键字span用来控制一次跨越的行数或列数, 如上面第三个子元素可以改写成:

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

找对齐

和父容器中所设置的对齐方式类似,针对个别子元素的对齐处理,我们可以按照行列两组属性进行分别处理:

/* 列对齐 */
.child:nth-child(1) {
  align-self: end;
}

/* 行对齐 */
.child:nth-child(2) {
  justify-self: end;
}

/* 采用缩写形式 */
.child:nth-child(3) {
  place-self: center center;
}

image.png

*隐式网格

灵活性是网格布局的一大优势,除了采用上述那种手动指定框架结构的方式,网格布局还有一套自动化布局的机制,这套机制称为**“隐式网格布局”**。当我们在网格定义的区域外放置子元素时,或因子元素数量过多而需要更多的网格线时,布局算法就会自动生成隐式网格。默认情况下这些隐式网格的大小也会随着内容尺寸不同而变化,而我们可以利用属性grid-auto-rowsgrid-auto-columns来控制隐式网格的大小。 考虑下面这个例子:

<div class="parent">
  <div class="child" style="background: red"></div>
  <div class="child" style="background: yellow"></div>
  <div class="child" style="background: green"></div>
</div>
.parent {
  display: grid;
  grid-auto-rows: 100px;
  grid-auto-columns: 100px;
}

通过手动在父容器中设置隐式网格大小为100x100的大小后,效果如下:

image.png
如果子元素引用了不存在的行号和列号,父容器会自动生成隐式网格以容纳所有子元素:

.child:first-child {
    grid-row-start: 1;
    grid-row-end: 2;
    grid-column-start: 1;
    grid-column-end: 3;
    background: red;
}

image.png

有了网格大小的控制,我们还需要位置的控制。默认情况下,子元素都是先将行填充满,容器大小不够的时候才会生成新的隐式行。如果要改变这一默认行为,我们需要使用grid-auto-flow属性来控制:

.parent {
  display: grid;
  grid-auto-rows: 100px;
  grid-auto-columns: 100px;
  grid-template-areas: "a b c" "d e f" "g h i";
  grid-auto-flow: column;
}

image.png

取值之术

介绍完所有的网格布局属性后,我们再来说一下各种属性的取值策略。

大小

在CSS中,我们通常使用px,em等取值单位进行属性大小的设置,对于灵活的布局需求来说,百分比也是常用的取值单位。这些单位在平常工作中似乎已经足够用了。不过,为了让布局能够更加灵活,网格布局中引入了一种新单位fr,它是fraction这个单词的缩写,意思是容器内剩余空间的分数比。考虑下面这个例子:

.parent {
  height: 100px;
  display: grid;
  grid-template-columns: 100px 1fr 100px;
}

我们通过设置100px 1fr 100px的布局框架,从而很轻松地就实现了两边宽度固定,中间自适应的效果。

image.png

如果要实现有比例关系的布局结构,还可以使用多个fr的取值:

.parent {
  width: 400px;
  height: 100px;
  display: grid;
  grid-template-rows: 100px 1fr 100px;
  grid-template-columns: 1fr 1fr 2fr;
  grid-template-areas: "a b c"
}

可以看到区域a,b,c之间的比例关系就是"1:1:2"的关系。

image.png

除了上述这个支持自适应的单位外,网格布局中还能够使用max-contentmin-content这组关键字来达到自适应的目的。要理解这两个关键字,首先需要理解内在尺寸(intrinsic size)和外部尺寸(extrinsic size)这两个概念。先说一下extrinsic size,它的相对值计算是相对于父容器对应的属性值。我们知道,width如果使用百分比单位,其计算值是相对于该元素所在的容器宽度的,比如父容器宽度100px, 子元素设置width: 20%,那么它的宽度就是100px * 20% = 20px。在css3中引入了intrinsic size则是相对于元素自身尺寸进行计算。max-contentmin-content就是相对于元素自身内容块进行计算的属性值。

min-content顾名思义是根据元素内容来设置的最小宽度大小,在英文句子中,通常是最长单词的那个长度,而中文中则是一个字的长度。比如下面这个例子:

.parent {
  display: grid;
  grid-template-columns: auto min-content auto;
}

image.png

可以看到,中间那个网格的那个宽度就等于scq000这个单词的长度。

min-content相对应,max-content会将尺寸设置成内容尺寸能达到的最大宽度。我们把代码改成下面这样:

.parent {
  display: grid;
  grid-template-columns: auto max-content auto;
}

image.png

有了这两个属性值,我们可以很容易地让布局区域根据内容进行自适应。

函数

函数是用来避免重复性工作的一种有效工具,在网格布局中提供了一些常用CSS函数来方便我们的工作。

第一个要介绍的是minmax这个函数。在设置网格框架的过程中,对于自适应的网格区域,我们都会设置一个最小值和最大值,这个函数就是用来实现这个目的的。

.parent {
    display: grid;
    grid-template-columns: 100px minmax(100px, 200px) 100px;
}

/* 最常用的情况是只设置最小,不设置最大值 */
.parent {
    display: grid;
    grid-template-columns: 100px minmax(100px, auto) 100px;
}

利用这个函数设置的网格布局可以做到很好的自适应,在页面伸缩过程中也能保证布局的稳定性。

另一个很有用的函数是fit-content,它实际上是min(maximum size, max(minimum size, argument))的简写,表示将元素宽度收缩到内容宽度。说得通俗点就是,使用这个函数后会尽量不占用多余的空间。如果内容的宽度小于fit-content中设置的长度,那么实际子元素宽度是内容宽度。如果内容宽度超出了fit-content中设置的长度,那么实际子元素宽度就是设置的那个长度。下面看两个例子:

.parent {
    display: grid;
    grid-template-rows: auto fit-content(200px) auto;
}

image.png

image.png

第一个句子中的长度超出了200px,那么此时中间网格的宽度是200px。而第二个例子中内容宽度不足200px,此时中间网格的宽度是句子实际占用的宽度。

最后要介绍的是repeat函数,它主要用来批量设置框架的间距,这个函数接受两个参数,第一个参数控制循环次数,第二个参数控制间距大小。让我们用这个函数改写一下上述例子:

.parent {
	display: grid;
	grid-template-rows: 100px 100px 100px;
	grid-template-columns: 100px 100px 100px;
}
/* 利用repeat函数改写 */
.parent {
	display: grid;
	grid-template-rows: repeat(3, 100px);
	grid-template-columns: repeat(3, 100px);
}

另外,第一个参数除了可以使用数字显示设置网格数量外,还能使用auto-fitauto-fill两个关键字自动分配空间。通常情况下,这两个关键字的使用效果都差不多,唯一的差别是空余空间的分配规则。搭配minmax函数可以看出区别,如下面这两个例子:

.parent {
	display: grid;
	width: 500px;
	height: 100px;
	grid-gap: 10px;
	grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}

.parent {
	display: grid;
	width: 500px;
	height: 100px;
	grid-gap: 10px;
	grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}

auto-fit

image.png

在单行布局的时候,如果有空余空间auto-fit会将它们平均分配到所有子元素中,而auto-fill会自动创建空白的列。

命名

我们在创建网格布局框架的时候,通过"画线"来指定基本布局结构。默认情况下,网格布局会给每一条网格线进行命名,命名顺序同书写顺序一致:从左到右,由上至下按数字命名。假设我们指定的是3x3的网格布局结构,那么包含边框线,就会生成4+4=8条线。

image.png
除了采用默认命名方式,我们还能够自定义网格线的名称以便于后续在子元素定位中使用。

.parent {
  display: grid;
  grid-template-rows: [row-a] 100px [row-b] 100px [row-c] 100px [row-d];
}

对齐

对齐是布局过程中一个不可缺少的步骤,它的取值是通过已有的关键字来指定的。其中用于网格布局中对齐的关键字有start,center, endstretch四个。 我们先从默认值stretch看起,这个关键字是伸展的意思,所以在默认情况下网格中的子元素会尽可能地填充满网格区域,由于是默认值所以平常写代码的时候就不太会可以去用这个关键字进行声明。 接着再来看一下常用的对齐取值策略,只需要记住一点就可以了:用属性justify-*,align-*来分别控制横轴和纵轴两个方向,属性值控制其对齐位置。 startcenterend三个属性值就分别对应了前中后三个位置。

.parent {
  display: grid;
}
.child:first-child {
  justify-self: start;
}
.child:first-child {
  justify-self: center;
}
.child:first-child {
  justify-self: end;
}

*排列

让我们重新回到隐式网格那部分,隐式的排列规则是通过指定grid-auto-flow这个属性来设置的。它的取值只有三个rowcolumndense。上面属性部分已经介绍过rowcolumn两个属性值,前者是按行优先来摆放子元素,后者是按列优先来摆放子元素。这里主要介绍一下dense这个属性值。通常情况下,排列子元素会按照顺序填充行或列,如果空间不足的时候,会换行或换列。而使用了dense属性后,会尽量使用空余空间,考虑下面这段代码:

.parent {
  display: grid;
  grid-template-columns: repeat(3, 100px);
}
<div class="parent">
  <div class="child">1</div>
  <div class="child">2</div>
  <div class="child">3</div>
  <div class="child">4</div>
  <div class="child">5</div>
</div>

默认情况下,显示效果是这样的:

image.png

当我们使用dense值进行排列的时候,就相当于开启“紧凑”模式,会尽可能利用空余空间。

.parent {
	display: grid;
	grid-auto-flow: row dense;
	/* 由于默认为按行排列,可省略为dense */
	grid-auto-flow: dense;
}

所以,显示效果如下:

image.png

总结

在这篇文章里,我通过拆解分类所有网格布局相关的知识点,希望能够为大家提供一个比较系统的运用网格布局的指导方法。在实际应用过程中,沿着"32构建之法”的这个套路来走,可以节约很多思考和决策时间。另外,由于这篇文章信息密度可能比较大,希望大家能够多多复习,并跟着例子实际操练几遍,这样在实际工作运用中才能如鱼得水。最后,送上一张思维导图,帮助大家能够一览本文所有的重点。

image.png

推荐阅读

grid.malven.co/

www.w3.org/TR/css-alig…

medium.com/@patrickbro…

www.zhangxinxu.com/wordpress/2…

css-tricks.com/difference-…

画图调试工具: www.mozilla.org/en-US/firef…

——本文首发于个人公众号,转载请注明出处———

微信扫描二维码,关注我的公众号
最后,欢迎大家关注我的公众号,一起学习交流。