CSS——网格布局(display: grid)
前面介绍了弹性布局,今天我们介绍一下网格布局。
什么是网格布局
CSS网格布局(CSS Grid Layout)是一种用于创建复杂网页布局的系统,它允许开发者以二维系统(行和列)来控制元素的布局。跟 Flexbox 类似,网格布局也是作用于两级的 DOM 结构。设置为 display: grid
的元素成为一个网格容器(grid container)。它的子元素则变成网格元素(grid items)。除此之外,另外四个重要的概念如下图所示。
- 网格线(grid line)——网格线构成了网格的框架。一条网格线可以水平或垂直,并且位于一行或一列的任意一侧。如果指定了 grid-gap 的话,它就位于网格线上。
- 网格轨道(grid track)——一个网格轨道是两条相邻网格线之间的空间。网格有水平轨道(行)和垂直轨道(列)。
- 网格单元(grid cell)——网格上的单个空间,水平和垂直的网格轨道交叉重叠的部分。
- 网格区域(grid area)——网格上的矩形区域,由一个到多个网格单元组成。该区域位于两条垂直网格线和两条水平网格线之间。
如何进行网格布局
设置 display: grid
只是网格布局的第一步,下面我们从 grid-template-columns
与 grid-template-rows
属性来逐步深入。
grid-template-columns 与 grid-template-rows 属性
grid-template-columns: 1fr 1fr 1fr
:“fr
”代表分数单位(fraction unit),我们这里可以简单的理解为“在被划分的空间中所占据的份数”,所以,这个效果就是:创建三个等宽的列。当然,我们可以采取其他单位,如:%、px等。这些单位可以混搭使用,如:gird-template-columns: 300px 1fr 2fr
,那么就会先创建300px宽的列,剩下的空间再分别分配1/3、2/3的空间给剩余的两列。不同单位的优先级如下:- 百分比(%):首先,浏览器会计算出可用空间的百分比部分。在这个例子中,第二列的宽度是容器宽度的90%。
- 固定单位(px等):然后,浏览器会为以像素为单位的列分配固定的空间。在这个例子中,第四列的宽度是100px。
- 分数(fr):最后,浏览器会将剩余的空间按照分数单位的比例分配给使用 fr 的列。在这个例子中,第一列和第三列分别占据1份和2份。
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> /* 创建了四个格子,父元素的背景色为黄色, 其余的为了设置从#111 --> #eee的渐变,并设置了半透明 */ .main-container { width: 1000px; background-color: yellow; } .grid-item:nth-child(1) { width: 100px; padding: 10px; border: 1px solid; margin-right: 10px; } .grid-container { display: grid; grid-template-columns: 1fr 95% 100px 2fr; grid-template-rows: 1fr; } .grid-item { background-color: #999; } div { color: red; font-size: 1.5rem; font-weight: bold; } </style> </head> <body> <main class="main-container"> <div class="grid-container"> <div class="grid-item" style="background-color: rgba(17, 17, 17,.5);">项目1</div> <div class="grid-item" style="background-color: rgb(85, 85, 85,.5);">项目2</div> <div class="grid-item" style="background-color: rgb(153, 153, 153,.5);">项目3</div> <div class="grid-item" style="background-color: rgb(238, 238, 238,.5);">项目4</div> </div> </main> </body> </html>
可以自己复制代码进行尝试,我们在开发者工具中看到:项目1的宽度为100px,项目2的宽度为950px,项目3的宽度为100px,项目4的宽度为16px。
首先看到确实复合前面的规则,但是在明明在分配 fr单位的盒子的宽度之前,父元素的空间就已经占满了,那它们的宽度是如何来的呢?
这里就要解释了:在父元素宽度超出之后,浏览器也知道没有宽度了,所有就不按照 fr 比例进行分配了,而是按照元素的宽度(包括内容、内边距,甚至是外边距)进行分配,比如项目1 指定宽度为 100px,所有分配给他100px,至于项目4 没有指定宽度,其内容只有几个文字,而这些文字的字号是用户代理样式表(浏览器默认加载的css文件,确保了在用户未设置样式时网页的可读性,其优先级低于作者样式表,即我们自己添加的css样式)中的字号——16px,与以往不同的是,这里浏览器会尽量压缩宽度,使文字换行显示(min-width,中文为一个汉字的宽度,英文为一个单词的宽度)。如果不做处理的话,这些内容会正常的超出显示(如上图所示)。
grid-template-rows
的同理。
网格轨道的创建——repeat()函数
该函数可以用于 CSS Grid 属性 grid-template-columns
和 grid-template-rows
中。repeat() 函数表示轨道列表的重复片段,允许以更紧凑的形式写入大量显示重复模式的列或行。如:grid-template-rows: repeat(3, 2fr 1fr)
:重复“2fr 1fr”这两个模式三次,总共创建6行。
repeat()函数
有两个参数:
第一个参数可以是以下三种之一:
- 数字(比如1,2,3)
- auto-fit关键字
- auto-fill关键字
第二个参数可选值包括:
- 长度值,可使用单位包括fr、px、em、%和ch等等
- min-content关键字
- max-content关键字
- auto关键字
- minmax()函数,其可以嵌套min()或者max()函数
- fit-content()函数
- 命名线
参数取值
-
<length>
正整数数值 -
<percentage>
百分比长度。- 如果是行(rows),则相对于网格容器的宽度,如果是列(columns),则相对于网格容器的高度。
- 如果网格容器的大小取决于网格元素,那么必须为关键字
auto
- 如果网格元素的大小超过了网格容器的大小,那么浏览器会对网格元素的大小进行调整
-
<flex>
带有fr
单位的非负尺寸指定轨道的弹性系数。任何被 <flex> 指定大小的轨道会根据其弹性系数按比例分配剩余空间。 -
关键字
max-content
首先介绍一下,min-content和max-content尺寸是根据内容来的,min-content是最小内容尺寸,中文的最小内容单位是一个汉字,英文的最小内容单位是单词,因此min-content最终宽度是所有这些最小内容单元最长的那个单元宽度;max-content是最大内容宽度,可以理解为文本内容不换行时候的宽度
。不过,min-content和max-content在实际开发的时候是不会相对于字符进行尺寸设定的,而是相对于图片或者内联性质的容器元素,比方说容器宽度不确定同时一行最多显示一个容器(min-content),或者所有元素在一行显示(max-content)。container{ grid-template-columns: repeat(3, min-content); }
min-content 关键字可将轨道设置为与其最小内容一样宽或一样高。通常是单词间没有额外空间时的尺寸。如下图:
-
关键字
min-content
max-content 关键字的作用基本上与 min-content 相反:它根据网格单元格中最大的内容来确定轨道大小。如下图:
-
关键字
auto
auto 关键字的最大值为 max-content,最小值为 min-content(auto 只有在与其他值混合时才会出现上述行为。如果单独使用 repeat(3, auto),其行为就像我们设置 repeat(3, 1fr) 一样)。.container{ grid-template-columns: repeat(3, auto 1fr); }
在这里,我们将有六列,每一奇数列的宽度设置为 auto。在下面的演示中,我们可以看到,在有足够空间的情况下,带有"auto"文本的 div 将在max-content时达到最大宽度,而 1fr div 则共享剩余空间。当浏览器变窄时,"auto"列继续变窄,直到达到min-content阈值。
-
关键字
auto-fill
.container { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); }
效果如下,默认宽度很宽的情况下,最后会有匿名的格子:
随着尺寸变小,列数会跟着动态变化,同时宽度自动填充Grid容器(因为设置了1fr)。弹性变化效果如图:
当我们使用auto-fill自动填充的时候,repeat()函数是不能和auto一起使用的,例如下面这种写法是无效的:.container { /* 无效 */ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)) auto; }
但是可以和长度只和百分比值一起使用,例如:
.container { /* 有效,最后一列的宽度始终为 20% */ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)) 20%; }
-
关键字
auto-fit
auto-fit 与 auto-fill 的行为是相似的,区别在于auto-fit 会把空的匿名格子进行折叠合并,而这个合并的 0px 大小格子可以认为具有单个格子轨道大小调整的功能,对了,其两侧的格子过道也会合并。在 auto-fill 自动填充的时候,如果 Grid容器的尺寸特别的宽,则最后会有一些空的格子占位:
但是auto-fit自动适应的时候,如果Grid容器的尺寸特别的宽,则最后会有一些空的格子会合并成 1个,且宽度是 0。auto-fit 区别示意:
-
maxmin()函数
minmax() 函数本身需要两个参数–最小值和最大值,中间用逗号隔开。因此,通过 minmax(),我们可以在灵活的环境中为轨道设置一系列可能的尺寸。
例如,我们可以将一列设置为minmax(40px, 100px)
,这意味着其最小宽度为 40px,最大宽度为 100px。minmax() 的两个参数都可以使用长度值,如 fr、px、em、% 和 ch,以及 min-content、max-content 和 auto。不过,最好至少为一个参数使用长度值,因为关键字不应该同时作为两个参数工作 。
下面代码设置了五列,每一列的最小宽度为60px,最大宽度为1fr:article { grid-template-columns: repeat(5, minmax(60px, 1fr)); }
minmax() 函数的参数也可以是 min() 或 max() 函数。这两个函数都接收两个参数。min()函数应用两个值中较小的值,而 max() 函数应用较大的值。这在响应式环境中非常有用。
比如说:
article { grid-template-columns: repeat(5, minmax(min(60px, 8vw), 1fr)); }
上面的代码设置了五列。在宽屏幕浏览器上,五列的间距均为 1fr。在较窄的设备上,列会越来越窄。一旦达到 60px 和 8vw 之间的较低值,就会停止缩小。 -
fit-content()函数
只有一个参数,只能为长度或者百分值<length> | <percentage>。
其底层原理不过多解释,效果可以描述为:“尺寸由内容决定,内容越多尺寸越大,最小为 min-content,最大不超过限定的尺寸”。.container { grid-template-columns: repeat(2, fit-content(100px) 40px) auto; }
网格元素的安放
三种语法:带编号的网格线
、命名的网格线
以及命名的网格区域
带编号的网格线
网格线编号从左上角为 1 开始递增,负数则指向从右下角开始的位置,如下图:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网格布局</title>
<style>
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(4, 1fr);
grid-gap: 10px;
}
.header {
grid-column: 1 / 3;
grid-row: 1 / 2;
}
.nav {
grid-column: 1 / 3;
grid-row: 2 / 3;
}
.main {
grid-column: 1 / 2;
grid-row: 3 / 5;
}
.sidebar-top {
grid-column: 2 / 3;
grid-row: 3 / 4;
}
.sidebar-bottom {
grid-column: 2 / 3;
grid-row: 4 / 5;
}
.grid-container>div {
background-color: #999;
}
</style>
</head>
<body>
<div class="grid-container">
<div class="header">Header</div>
<div class="nav">Nav</div>
<div class="main">Main</div>
<div class="sidebar-top">Sidebar Top</div>
<div class="sidebar-bottom">Sidebar Bottom</div>
</div>
</body>
</html>
其中,grid-column
属性是 grid-column-start
和 grid-column-end
的简写,中间使用 “/” 分隔开。
效果如下:
span关键字
作为 grid-column
和 grid-row
属性的值,后面跟正整数
,表示在前面属性的方向上占据多少个轨道。由于没有指定是具体的哪几行和哪几列,会根据浏览器的布局算法进行布局(这里先不解释)。
以下表示占据两列一行:
.item {
grid-column: span 2;
grid-row: span 1;
}
命名的网格线
与前面的带编号的命名方式相似,只不过是为那些编号起了名字(就好像学号和姓名一样,虽然有点“倒反天罡”了,但是大致是这样的意思),不过不必要给所有的网格线都起名字,就相当于并不是所有的人都是学生。结合下面的例子:
.container {
display: grid;
grid-template-columns: [left-start] 2fr
[left-end right-start] 1fr
[right-end];
grid-template-rows: repeat(4, [row] auto);
grid-gap: 1.5em;
max-width: 1080px;
margin: 0 auto;
}
header,
nav {
grid-column: left-start / right-end;
grid-row: span 1;
}
.main {
grid-column: left;
grid-row: row 3 / span 2;
}
.sidebar-top {
grid-column: right;
grid-row: 3 / 4;
}
.sidebar-bottom {
grid-column: right;
grid-row: 4 / 5;
}
我们把纵向的1、2、3号网格线分别命名为 left-start、left-end(right-start)、right-end,这样我们就可以使用 left-start、left-end(right-start)、right-end代替前面的数字,其语义化更好。
这里有个彩蛋:-start
和 -end
后缀作为关键字
,定义了两者之间的区域。如果给元素设置 grid-column: left,它就会跨越从 left-start 到 left-end 的区域
。
此外,我们还在 repeat()函数
中使用了这种写法,由于repeat()函数是用来创建轨道的,我们这里 grid-template-rows: repeat(4, [row] auto)
在 auto
的前面添加 [row]
就是在为轨道的前面的网格线起名字,所以导致了最后一根网格线是没有名字的,只有编号 “5”。还注意到 grid-row: row 3 / span 2
的操作,虽然我们设置了四条名字一样的网格线,但是我们可以通过“名字 第几条同名线
”的组合来定位具体是哪根线。
命名网格区域
不用计算或者命名网格线,直接用命名的网格区域将元素定位到网格中。实现这一方法需要借助网格容器的 grid-template-areas
属性和网格元素的 grid-area
属性。
.container {
display: grid;
grid-template-areas: "title title"
"nav nav"
"main aside1"
"main aside2";
grid-template-columns: 2fr 1fr;
grid-template-rows: repeat(4, auto);
grid-gap: 1.5em;
max-width: 1080px;
margin: 0 auto;
}
header {
grid-area: title;
}
nav {
grid-area: nav;
}
.main {
grid-area: main;
}
.sidebar-top {
grid-area: aside1;
}
.sidebar-bottom {
grid-area: aside2;
}
grid-template-areas 属性使用了一种 ASCII art 的语法,可以直接在 CSS 中画一个可视化的网格形象。该声明给出了一系列加引号字符串,每一个字符串代表网格的一行,字符串内用空格区分每一列。在这个例子中,第一行完全分配给了网格区域 title,第二行则分配给了 nav。接下来两行的左列分配给了 main,侧边栏的板块分别分配给了 aside1 和 aside2。用 grid-area 属性将每个网格元素放在这些命名区域中。
注意:每个命名的网格区域必须组成一个矩形。不能创造更复杂的形状,比如 L或者 U型。
还可以用句点(.)作为名称,这样便能空出一个网格单元。比如,以下代码定义了四个网
格区域,中间围绕着一个空的网格单元。
grid-template-areas: "top top right"
"left . right"
"left bottom bottom";
结语
这篇文章已经介绍完网格布局的最基础的部分,也请关注下一篇(实战篇)。感谢大家的支持,如有错误,恳请指出,希望与大家共同进步!