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。(其实前两者没有很严格的先后关系,只是为了与后面的相对单位进行区分)
- auto关键字:按照这一列(行)的盒子最宽(高)的那个来分配这一列(行)的宽度(高度),这个宽度(高度)包括这个盒子的内容区域、内边距、边框以及外边距。
- 分数(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
的简写,中间使用 “/” 分隔开,grid-rows
属性同理。
效果如下:
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";
网格布局中的对齐属性
前面我们主要是创建了网格布局以及进行元素的放置,但这仅仅是网格布局最基础的部分(红色框中的部分),下面我们来介绍一部分基本的属性(绿色框中的,看起来很多,实际上也就三组),剩下的较为复杂的属性我们留到下一篇再讲。
***-items属性(添加在网格容器上)
控制网格单元内容在网格单元的对齐方式
- 垂直对齐——
align-items
(默认stretch
,拉满整个单元)
取值:
start
、end
、center
、stretch
- 水平对齐——
justify-items
取值同上 - 简写属性——
place-items
取值:<align-items> <justify-items>
***-content属性(添加在网格容器上)
控制整个网格布局在网格容器中的对齐方式
- 垂直对齐——
align-content
取值:start
、
end
、
center
、
space-between
(第一项与起始点齐平,最后一项与终止点齐平 ,中间间距均匀分布)、
space-around
(项目的两端的宽度相等,致使两个项目之间的间距是最边缘的项目与边缘的边距的两倍)、
space-evenly
(项目与项目之间 ,项目与边缘之间的间距都是相等的)、
stretch
- 水平对齐——
justify-content
(默认normal
,正常文档流)
取值同上 - 简写——
place-content
取值:<align-content> <justify-conten>
***-self属性(添加在网格单元内的容器上)
控制单个的网格单元的内容可以有与其他的网格单元不同的对齐方式(整体上与***-items
属性一致)
- 垂直对齐——
align-self
取值:start
、end
、center
、stretch
- 水平对齐——
justify-self
取值同上 - 简写属性——
place-self
取值:<align-self> <justify-self>
学了flex弹性布局的朋友们对这些属性可能会比较熟悉,基本上是一致的。如果还有没学过的,请移步CSS——弹性盒子布局(display: flex),这也是鄙人的文章(嘻嘻)。此外,本节内容就不附带演示了,大家参考上述文章即可。
结语
这篇文章已经介绍完网格布局的最基础的部分,也请关注下一篇(实战篇)。感谢大家的支持,如有错误,恳请指出,希望与大家共同进步!