目录
网格容器 display:grid、display:inline-grid
网格轨道 grid-template-rows、grid-template-columns
网格布局非常类似于表格布局,它们都是通过定义n行m列,来将容器划分为n*m个单元格。
下面是表格布局
表格布局,是通过tr标签来定义行,通过td标签来定义每行的单元格(相当于定义列)
下面是网格布局
网格布局,是为容器元素添加display:grid样式,来把容器元素变为网格容器,通过网格容器的grid-template-rows样式来定义各行高度,grid-template-columns样式来定义各列宽度。而网格容器的直接子级元素默认依次放入每个单元格中。
对比来看,表格实现行列依赖于标签结构,网格实现行列依赖于样式。
从页面渲染性能角度来看,肯定是网格的性能更佳,因为同样渲染一个单元格,网格中单元格的元素的层级更浅。一个完整表格单元格层级应该是table>tbody>tr>td,而网格单元格的层级是.container > .gird。
另外网格容器可以更好地支持单元格的布局,比如单元格的跨行跨列,比如单元格在网格中的位置。
我们再用弹性布局来实现上面的网格效果
可以发现,弹性布局似乎更加简单地实现了网格效果 ,但是这种网格效果本质是一维的,而不是二维的。
之前,我们学习弹性布局时,了解到弹性布局其实是一维布局,所谓一维布局,即弹性项目元素只会沿着弹性容器的主轴方向排列,虽然上面例子中,弹性项目看上去是二维的,但是实际上,第二行,第三行的弹性项目元素都是被挤下来的,如果我们不设置换行wrap,则这些弹性项目元素将全部在一行上排列。
所以,弹性布局只能让元素在一维轴线上实现很好的布局,而无法实现真正二维网格的效果,比如弹性布局中弹性项目就很难实现单元格跨行,如让下图中1,4,7单元格合并。
因为弹性布局本质是一维布局,即1,4,7本质是一行上的,它们之间隔着2,3和5,6,所以无法轻松地实现合并。
而网格布局是二维布局,它的单元格是由多条行线和列线分隔出来的,是真正二维意义上的网格效果。
我们可以很容易地依赖于网格线来完成单元格跨行跨列,如网格布局实现1,4,7合并
当我们为一个元素添加如下样式之一
该元素就变成了网格容器。
在标准流中:
整个网格系统都在网格容器的 content-box 中。
所谓网格轨道,即网格行和网格列的统称。
我们可以通过
上例中:
我们需要注意的是网格轨道并非真实的DOM元素,而是一种逻辑概念,所以它不会被浏览器渲染出来,上例中网格轨道的效果图是通过浏览器的grid布局检测模拟出来的。
grid-template-rows、grid-template-columns不仅支持固定尺寸,还支持百分比尺寸、fr尺寸、auto尺寸、min-content、max-content尺寸、minmax()尺寸
这里百分比尺寸的基数是网格容器的尺寸。如网格行的百分比尺寸的基数,就是网格容器的content-box的height,网格列的百分比尺寸的基数,就是网格容器的content-box的width
fr是一种单位,每个定义了fr尺寸的网格轨道会按fr比例分配网格容器的剩余可用空间。
比如上例中,将三个网格列尺寸分别定义为:1fr 2fr 1fr
即表示,将网格容器的剩余可用空间分为四等分,第一列、第三列占1分,第二列占2分。
咋一看,似乎fr尺寸和百分比尺寸没有区别,其实二者还是存在区别的。
百分比的基数是网格容器尺寸,fr是网格容器剩余空间的占比,所以二者基数不同。
我们通过下面例子来理解百分比尺寸和fr尺寸的区别:
上例中,为网格行之间、网格列之间添加都添加了10px的网格间距,通过结果可以发现:
- 使用百分比尺寸的网格行的尺寸并没有因为网格间距的加入而改变,所以,网格行尺寸 + 网格间距,超出了网格容器范围;
- 而网格列使用的是fr尺寸,它的基数是网格容器剩余空间,这里的剩余空间 = 网格容器尺寸 - 网格间距,这保证了网格列不会超出网格容器范围。
网格轨道尺寸设为auto,意思是占据网格容器剩余全部空间。
- 百分比尺寸结合auto尺寸可以保证网格轨道不会超出网格容器范围,如上例中网格行尺寸设置。
- 而fr尺寸也是基于网格容器剩余空间计算的,所以必然和auto尺寸存在冲突,而通过上例网格列可以发现,fr尺寸直接分配完了剩余空间,导致auto分配时,就没有剩余空间了。
需要注意的是虽然auto分得尺寸为0,但是也会生成网格轨道。如上例中,第二列左侧也有gap,而gap只存在于网格轨道之间。
Intrinsic Sizing In CSS - Ahmad Shadeed (ishadeed.com)
一个元素的width、height设置可以分为两种情况:
我们设置元素的width、height为固定尺寸,如100px,或者设置为百分比尺寸,如50%,都算是外部尺寸。
元素的外部尺寸的不会受自身内容的影响。(行内元素由于不可以设置width,所以不在此列。)
而有时候,我们期望元素的width、height可以适应其内容,CSS为此设计了三种特殊内部尺寸值:
当一个元素的width为min-content时:
当一个元素的width:max-content时,则表示将该元素的宽度设置为内容的宽度
当我们设置元素width:fit-content时,注意观察上例中盒子模型width的变化,发现:
元素可用空间,一般指的是元素所在父盒子内容区的宽高,例子中,元素可用空间即container容器的内容区宽度。
所以网格布局中,grid-template-rows或grid-template-columns轨道尺寸设置为min-content或max-content时,会根据轨道中网格项目内容来决定轨道实际尺寸。
上图网格容器中创建了一行一列,且网格行尺寸为max-content,即为网格项目内容实际高度,网格列尺寸为min-content,即为网格项目内容中最长的英文单词的长度。
minmax是css的函数,用于定义网格轨道的尺寸范围(闭区间);
minmax函数包含两个参数,依次是网格轨道的最小尺寸min、最大尺寸max,表示:
minmax的作用其实非常类似于元素内部尺寸fit-content,只是minmax的可以自定义最小和最大尺寸。
如果我们设置轨道尺寸为 minmax(min-content, max-content),其实就是设置轨道尺寸为fit-content,但是轨道尺寸却不支持fit-content。
minmax函数参数可以是固定尺寸,也可以是百分比尺寸,fr尺寸,auto尺寸,需要注意的是:
minmax函数结果
为min尺寸。如果minmax函数的最大尺寸参数值(如50px)小于最小尺寸参数值(如100px),则最大尺寸参数(如50px)作废,minmax函数结果固定是最小尺寸参数值(如100px)。
可以发现,当将fr尺寸作为minmax函数最小尺寸参数时,则minmax无效。
当将fr尺寸作为minmax函数最大尺寸参数时,则等价于布局容器剩余空间。
可以发现,minmax的最大尺寸参数设为auto时,并不等价于max-content,而是占满容器剩余空间。
可以发现,minmax的最小尺寸参数设为auto时,等价于轨道中项目元素的min-width/min-height。
css - What happens when minmax is: minmax(auto, auto) - Stack Overflow
如果轨道中项目元素没有in-width/min-height,则auto等价于轨道的min-content。
如果我们的网格行都是等高的、网格列都是等宽,假设需要五行五列,则写法如下
grid-template-rows:1fr 1fr 1fr 1fr 1fr;
grid-template-columns:1fr 1fr 1fr 1fr 1fr;
如果需要十行十列,则
grid-template-rows:1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-columns:1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
显然这种写法非常不友好,所以我们需要使用css的repeat函数。
css的repeat函数专门用于帮助grid-template-rows、grid-template-columns来生成重复尺寸。
repeat函数接收两个参数,第二个参数是要被重复的网格轨道尺寸,第一个参数是重复次数
需要注意的是,repeat函数的第二个参数既可以是一个尺寸,也可以多个尺寸
如上例中,grid-template-columns:repeat(2, 2fr 3fr) 相当于生成 grid-template-columns: 2fr 3fr 2fr 3fr
还有需要注意的是,repeat函数的结果就是一串尺寸片段,我们可以继续在后面拼接新的尺寸
repeat函数的第一个参数还支持如下特殊值:
mdn上关于auto-fill、auto-fit的介绍非常晦涩,我们可以参考
[译] CSS Grid 之列宽自适应:`auto-fill` vs `auto-fit` - 掘金 (juejin.cn)
auto-fill、auto-fit的作用是啥?
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Documenttitle>
- <style>
- .grid {
- display: grid;
- grid-template-columns: repeat(12, 1fr);
- }
-
- .grid > div {
- height: 100px;
- }
- style>
- head>
- <body>
- <div class="grid">
- <div>1div>
- <div>2div>
- <div>3div>
- <div>4div>
- <div>5div>
- <div>6div>
- <div>7div>
- <div>8div>
- <div>9div>
- <div>10div>
- <div>11div>
- <div>12div>
- div>
- body>
- html>
上述代码,我们可以得到一个被分为12个等宽列轨道的网格容器。
并且网格容器的宽度是动态的,这意味着分给12个等宽列轨道的空间也是变化的。
此时为了避免列轨道的宽度太小,导致轨道项目元素的内容溢出,我们需要为列轨道设定一个最小宽度。
grid-template-columns: repeat(12, minmax(100px, 1fr));
上面代码的意思是,将网格容器分为等宽的12列:
这虽然可以保轨道列中项目元素不会被挤压变形,但是可能会导致部分网格轨道超出网格容器范围。
那么有没有一种办法,既能保证网格轨道不会超出网格容器范围,也可以保证网格列的最小宽度呢?
为了保证所有网格轨道的完整显示,只能将超出网格容器的网格单元换行显示。
即,此时网格容器上的轨道列数目应该是动态的,而不是固定的。
此时,我们可以使用repeat函数的auto-fill和auto-fit来实现动态轨道列数目。
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
上面代码的意思是,在网格容器的一行上尽可能多(即采用最小尺寸,如100px)地放入(不定数目auto-fill个)网格轨道,对于一行放不下的网格单元采取换行显示,保证其不会超出网格容器范围。
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
auto-fit同理。
auto-fill与auto-fit的区别在于:
当网格容器的尺寸大于全部最小尺寸模式下的网格单元尺寸之和,比如网格容器1904px宽度,而12列等宽轨道最小尺寸之和为1200px,此时网格容器还剩余704px宽度空间:
也就是说,auto-fill和auto-fit在:
网格行与网格列相交便形成了网格单元,网格单元是grid布局的最小单元。网格单元和网格轨道一样也并非真实DOM元素,只是一种逻辑概念,所以它不会被浏览器渲染出来。
如上小节例子中,网格容器被分为3行3列,9个网格单元。
网格容器元素的直接子元素就是网格容器的网格项目,类似于弹性容器与弹性项目的关系。
默认情况下,网格项目会按代码顺序依次放入网格容器的网格单元中。
网格轨道是由网格线组成的,每个网格轨道由两条网格线组成。网格线不是真实DOM,所以不会被浏览器渲染。
网格线默认通过数字索引(从左往右,从上往下,起始索引1)引用,也可以通过自定义名字来引用。
关于为网格线自定义名字:
grid-template-rows、grid-template-colums用于定义网格轨道,而每个网格轨道由两条网格线构成;
grid-template-rows、grid-template-colums 不仅支持网格轨道数目、尺寸,还支持定义组成网格轨道的网格线的名字,语法如下:
grid-template-rows:[lineName1] size1 [lineName2] size2 [lineName3] ...
特点就是一根网格轨道夹在两根网格线之间。
对于使用repeat函数生成的网格轨道,我们可以
grid-template-rows:repeat(3, [r-start] size [r-end])
此时实际生成结果为
grid-template-rows:[r-start 1] size [r-end 1] [r-start 2] size [r-end 2] [r-start 3] size [r-end 3]
此时网格线 r-end 1和r-start 2其实同一条网格线。
之前我们说过网格单元是被网格容器中的网格轨道分隔出来的,而本质上来说,网格单元其实是被网格容器中的网格线分割出来的。
我们可以用四条网格线来描述网格容器中的任意一个网格单元或多个网格单元。网格线引用包括:
这四个样式都属于网格项目元素,而不是网格容器。
网格项目元素通过四条网格线,来设定自身占据那个网格单元。
如下图,通过默认的数字索引网格线,来定义网格项目位置。
如下图通过自定义网格线名字来定位网格项目位置
如下通过repeat下的网格线自定义名字来定位网格项目位置。
上面设定网格线的方式比较繁琐,我们可以进行简化,即使用复合语法:
其实,我们还可以继续简化网格线引用,即使用进一步的复合语法:
grid-area:grid-row-start / grid-column-start / grid-row-end / grid-column-end
我们需要注意的是,网格线不仅可以描述一个网格单元,还可以描述多个网格单元,即让一个网格项目占据多个网格单元
另外,我们目前都是基于四条网格线来完成的网格单元描述,这种描述方式其实不太好在脑海中想象,我们可以只定义两条起始网格线,另外两条结束网格线通过偏移来描述。
grid-row-end:span 2; // 含义是相对于grid-row-start网格线向下偏移两个网格单元
grid-column-end:span 2; // 含义是相对于grid-column-start网格线向右偏移两个网格单元
支持简写形式
通过网格线描述网格单元其实使用起来语义化非常差
grid-area: 1 / 2 / span 2 / span 2
比如第一眼看到上面样式,我们很难直观感受到该项目所处网格单元的位置。
所以,网格容器提供了 grid-template-areas 样式直接描述网格单元,网格项目通过grid-area直接引用网格区域即可。
如果网格区域对应多个网格单元,则直接将处于同一个网格区域内的网格单元区域名称改为一致即可。
对于用不到区域命名的网格单元,我们可以用.来作为其区域名
grid-template是grid-template-rows、grid-template-columns、grid-template-areas的复合写法。
下面以三行三列的网格容器定义为例:
grid-template复合写法一:网格区域 + 网格轨道
grid-template:"grid-template-areas#1" grid-template-rows#1
"grid-template-areas#2" grid-template-rows#2
"grid-template-areas#3" grid-template-rows#3 / grid-template-columns;
grid-template复合写法二:网格线 + 网格轨道
grid-template: grid-template-rows / grid-template-columns
grid-template复合写法三:网格线 + 网格区域 + 网格轨道
grid-template:[line1] "grid-template-areas#1" grid-template-rows#1 [line2]
[line3] "grid-template-areas#2" grid-template-rows#2 [line4]
[line5] "grid-template-areas#3" grid-template-rows#3 [line6] / grid-template-columns;
grid-template:none
表示网格行列隐式生成,使用grid-auto-rows、grid-auto-columns来定义网格行列尺寸。
我们通过 grid-template-columns
和 grid-template-rows
创建出来的轨道,称为显示网格(轨道);但是这些轨道不一定能容纳所有的网格项目, 浏览器根据网格项目的数量计算出来需要更多的轨道, 就会自动生成新的轨道来容纳多出来的网格项目, 而这些自动生成的轨道被称为隐式网格(轨道)。
另外,如果我们未给网格容器定义显示网格轨道,则浏览器会根据网格项目自动创建隐式网格轨道。
隐式网格轨道的尺寸通过如下属性控制:
我们可以将这两个属性理解为网格轨道的默认尺寸设置,即:
需要注意的是,隐式创建的轨道只能是行轨道或列轨道之一,不可能同时创建两个方向的隐式轨道,而具体创建哪个方向的隐式轨道,由属性 grid-auto-flow 决定,该属性有如下值:
当grid-auto-flow:row时,只能创建隐式行轨道。
当grid-auto-flow:column时,只能创建隐式列轨道。
grid-auto-flow 还决定了 网格容器中 网格项目元素的排列方向
上例中,由于网格容器没有定义显示网格轨道,所以默认采用隐式网格轨道尺寸,然而这里也没有自定义隐式网格轨道,因此采用默认的隐式网格轨道尺寸auto。auto的含义是占据剩余空间。
这里因为grid-auto-flow是row方向,所以网格项目逐行填充,必要时新增行,列数目不会变化,因此grid-auto-columns:auto会产生占据网格容器全部宽度的唯一列,而grid-auto-rows:auto会产生平分网格容器高度的多个行。
grid-auto-flow:column同理
上例中,网格容器只有2行2列,理论只能放4个项目元素,但是实际上有9个项目元素,所以必然有5个项目元素放不下,此时只能生成隐式网格轨道来放。
需要注意的是,此时grid-auto-flow:row决定了只会创建隐式网格行轨道,不会创建隐式网格列轨道。
我们可以这样理解:
grid-auto-flow: row; // 只会新增行,不会新增列
grid-template-rows: repeat(2, 100px); // 显示行使用显示网格尺寸
grid-auto-row: auto; // 隐式行使用隐式网格轨道尺寸
grid-template-columns: repeat(2, 100px);
grid-auto-columns: auto;// 没有新增列,所以不需要使用隐式网格轨道尺寸
grid-auto-flow不仅可以控制网格项目的排列方向及隐式网格创建,还可以控制网格项目的包装模式。
grid-auto-flow实际上有四种值:
其中row、column是稀疏包装模式,row dense、column dense是密集包装模式。
我们通过几个例子来理解稀疏包装模式和密集包装模式:
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Documenttitle>
- <style>
- .grid {
- display: grid;
- grid-template-rows: repeat(4, 100px);
- grid-template-columns: repeat(4, 100px);
- }
-
- .red {
- grid-area: 1 / 2 / 2 / 3;
- background-color: red;
- }
-
- .blue {
- grid-area: 2 / 1 / 3 / 3;
- background-color: blue;
- }
-
- .green {
- grid-row: 1 / 3;
- background-color: green;
- }
-
- .yellow {
- grid-row: 1 / 2;
- background-color: yellow;
- }
- style>
- head>
- <body>
- <div class="grid">
- <div class="red">1div>
- <div class="blue">2div>
- <div class="green">3div>
- <div class="yellow">4div>
- div>
- body>
- html>
上面例子中,网格容器就是稀疏包装模式,因为默认的grid-auto-flow是row,此时我们发现yellow项目只需要grid-row: 1 / 2,即在第一行就行,而第一行刚好还剩两个空白网格单元,但是yellow项目却没有被放入第一个空白网格单元中。
这种情况就是稀疏包装模式导致的。
如果我们将grid-auto-flow改为row dense,则可以让yellow项目放入第一个空白网格单元中。
还有一个例子
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Documenttitle>
- <style>
- .grid {
- display: grid;
- grid-template-rows: repeat(4, 100px);
- grid-template-columns: repeat(4, 100px);
- }
-
- .red {
- grid-area: 1 / 2 / 2 / 3;
- background-color: red;
- }
-
- .blue {
- grid-column: 2 / 3;
- background-color: blue;
- }
-
- .green {
- grid-column: 1 / 2;
- background-color: green;
- }
- style>
- head>
- <body>
- <div class="grid">
- <div class="red">1div>
- <div class="blue">2div>
- <div class="green">3div>
- div>
- body>
- html>
green项目只需要占据grid-column: 1 / 2,即第一列中某个网格单元,但是green却没有占据第一行第一列网格单元,这也是稀疏包装模式导致的。
我们只需要让grid-auto-flow变为row dense即可
至于稀疏包装模式为什么会导致上述现象,这涉及到网格项目自动放置算法,可以借鉴如下资料
CSS 网格布局中的自动定位 - CSS(层叠样式表) | MDN (mozilla.org)
css网格_CSS网格中自动放置算法的循序渐进指南 - 灰信网(软件开发博客聚合) (freesion.com)
我这边做了一个半成品总结:
通过前面两个例子,大家可以发现,空白网格单元的出现,必然伴随着 定行不定列 或者 定列不定行 的网格项目,所谓
定行不定列:即只定义了grid-row,未定义grid-column
定列不定行:即只定义了grid-column,未定义grid-row
无论是稀疏模式,还是密集模式,定行不定列项目、定列不定行项目都不会占用已被占用的网格单元,即它们都不会和其他项目共享网格单元,如果它们首选的网格单元已被占用,则会换行或换列找下一个符合要求的网格单元。
最关键的是,稀疏模式下定行不定列项目之间、定列不定行项目之间会产生约束,在DOM中,
- 后渲染的定行不定列项目 一定排在 先渲染的定行不定列项目 之后
- 后渲染的定列不定行项目 一定排在 先渲染的定列不定行项目 之后
这是产生空白网格的关键原因。
而密集模式下,上面两条约束就失效了。
网格间距指的是网格轨道之间的间距,而不是网格项目之间的间距。
网格间距可以通过网格容器上的样式属性定义:
我们需要注意的是,如果网格容器具有固定width、height的话,则网格间距的加入可能会导致网格轨道溢出
如上例中,网格容器内容区是300*300尺寸,而网格容器的行轨道尺寸之和是300px,列轨道尺寸之和也是300px,刚好填满网格容器内容区。但是此时,我们又给行轨道之间加入10px间距,列轨道之间加入10px间距,这就导致网格系统多出了水平、垂直方向各多出了20px,导致了网格轨道溢出网格容器。
所以我们在使用网格间距时,一定要注意设置网格轨道的尺寸要兼容网格间距,避免溢出。
我们可以在计算网格轨道尺寸时,手动去除掉网格间距尺寸,但是这种计算太麻烦
因此,我们可以借助fr尺寸,auto尺寸来实现网格轨道尺寸自动减去网格间距
最后,我们的row-gap、column-gap还可以复合写为gap:row-gap column-gap
如果网格容器划分完轨道后,依旧还能剩余空闲空间,如上例中,网格容器300px*300px,列轨道尺寸总计200px,行轨道尺寸总计100px,则在垂直方向上还剩200px空闲空间,在水平空间上还剩100px空闲空间。
此时我们就可以基于网格容器,对网格单元进行水平方向、垂直方向的对齐操作。
具体效果看上面动图。
另外,justify-content、align-content可以复合写为
place-content:align-content justify-content
网格项目的对齐,即网格项目在网格单元中的对齐。它分为两类:
统一对齐,需要在网格容器上设置如下属性:
justify-items、align-items默认值为stretch,即当网格项目未定义width、height时,默认被拉伸填充网格单元。
统一对齐对网格容器中所有网格项目有效。
另外,justify-items、align-items可以复合写为
place-items:align-items justify-items
单独对齐,需要在网格项目上设置如下属性:
justify-self、align-self的功能和justify-items、align-items基本一致,只是justify-self、align-self只能作用于所在的网格项目。
另外, justify-self、align-self可以复合写为
place-self:align-self justify-self