深刻浅出grid布局

原文发布于 github.com/ta7sudan/no…, 如需转载请保留原做者 @ta7sudan.css

注意, 由于 grid 标准还在修订中, 如下内容可能会随着标准的改变而过期(好比听说以后的标准可能会将 grid-row-gapgrid-column-gap 改成 row-gapcolumn-gap, 以便与 column 布局统一), 固然我也会尽可能及时修正. 如下内容写于 2018/2/27.html

本文中提到的 content-box 均是在未改变 box-sizing 的状况下适用.css3

关于 grid 的应用场景, 或者说比其余布局有什么优点? 在有既要按水平方向进行对齐, 同时又要保证垂直方向的对齐时, 可使用 grid 布局.git

关于 grid 的建议是, 若是面向的用户是最新的浏览器, 那基本能够用于生产环境, 不然不建议用于生产环境.github

在介绍 gird 布局以前咱们先来看几幅图算法

img101

整个最大的整块矩形块咱们称做网格容器(grid container), 也是咱们的布局容器, 即容器里面的元素都按照网格进行布局. 图中的虚线咱们称为网格线(grid line), 网格线是一个布局时的参考线, 并不会被实际渲染出来. 网格线带有一个属性, 那就是编号, 即每一个网格线都是有本身的序号的.浏览器

img102

像这样, 不管水平方向仍是垂直方向的网格线都有本身的编号. 编号是网格线固有的属性, 无需开发者本身定义, 默认从 1 开始, 从上往下依次递增, 从左往右依次递增(但也不必定, 还与书写模式有关). 网格线能够有本身的名字, 名字由开发者定义, 具体参考后文.bash

网格容器被网格线分隔成了 3x3 个单元格, 每一个单元格咱们称为网格单元(grid item). 网格线也把网格容器分红了三行三列, 这里行和列咱们都称为网格轨道(grid track). 其中深色的矩形块 One, Two, Three, Four 咱们称为网格项目(grid item).ide

那网格单元和网格项目有什么区别? 答案是网格单元不是一个元素, 只是元素定位的参考系, 是一个逻辑概念, 不会被渲染到页面. 网格项目是咱们实际渲染的元素, 网格项目根据网格单元进行定位, 网格项目能够占据一个网格单元, 也能够占据多个网格单元, 像下面这样.wordpress

img104

能够看到, 这里的 One, Two 等矩形块都是网格项目, 这才是实际渲染出来的样子, 它们根据网格单元进行定位, 有些占据一个网格单元, 有些占据多个网格单元, 多个网格项目也可能重叠占据同一个网格单元.

img103

可是网格项目不只仅能够根据网格单元进行定位, 还能够根据网格区域进行定位. 咱们能够从一片连续的网格单元中取出一个矩形, 如红框所示. 咱们能够把这四个网格单元定义成一个网格区域(grid area), 而后让一个网格项目占据一块网格区域(目前只支持矩形的网格区域, 还不支持 L 型的网格区域).

有人可能已经注意到, 第三幅图中的网格项目之间有些空白间隔, 咱们把这些间隔叫作网格间距(gutter). 那网格间距是否占用网格轨道的宽度呢? 下图能够更清楚的代表这点.

img105

能够看到, 蓝色的是网格单元, 网格单元所处的地方是网格轨道, 而网格间距并无占用网格轨道的空间.

OK, 经过以上几幅图, 咱们大概能够知道, 网格容器, 网格轨道, 网格线, 网格单元, 网格区域, 网格间距都是逻辑概念, 是网格容器内部元素定位的参考系, 它们(除了网格容器和网格间距)不会被实际渲染出来, 而网格项目则是网格容器内部实际存在的元素, 会被渲染出来.

整个 grid 布局的过程就像是在一个事先定义的网格里面铺地砖, 咱们把网格项目铺在网格单元上, 一个网格项目能够占据一个或多个网格单元(为了便于理解, 这里咱们先用占据这个词, 但准确说并非网格项目占据网格单元, 而是指定网格项目的定位区域一个或多个网格单元. 以后的内容中会更详细地解释这点), 网格项目之间也能够重叠. 因此使用 grid 布局的基本套路就是: 先定义网格容器, 再定义网格单元, 而后指定网格项目怎么个"铺"法.

须要注意的是, 咱们说的定位是指网格项目的 margin-box 摆放在网格单元/网格区域之中, 网格单元/网格区域就像一个 BFC 那样, 使得网格项目的外边距不和其余网格项目的外边距发生折叠.

这个图中清晰地代表了一个网格项目的 margin-box 位于一个网格区域(三个网格单元)之中.

几个术语

接着咱们来了解一些术语, 其中有些前面咱们已经提到过, 不过这里给出一些更具体的定义.

  • 网格容器(grid container), display: grid;display: inline-grid; 的元素, 整个网格系统都在网格容器的 content-box 中, 而网格容器对外(其余元素)依然是块级元素或者行内元素. 这个元素的全部正常流中的直系子元素(包括匿名盒子)都将成为网格项目(grid item) (W3C: Each in-flow child of a grid container becomes a grid item). display: grid; 会建立一个GFC(grid formatting context), 相似 BFC 的东西.
  • 网格线(grid line) 是网格容器的水平或垂直分界线. 网格线存在于列或行的任意一侧. 它们能够经过数字索引(从1开始)来引用或者经过开发者自定义的名字来引用. 做为元素定位时的参考系, 是一个逻辑意义上的实体, 不会被渲染.
  • 网格轨道(grid track) 是任意两条相邻网格线之间的空间, 即一行或者一列, 以后简称轨道/行/列. 做为元素定位时的参考系, 是一个逻辑意义上的实体, 不会被渲染.
  • 网格单元(grid cell) 是行和列相交的空间, 是 grid 布局的最小单元. 做为元素定位时的参考系, 是一个逻辑意义上的实体, 不会被渲染.
  • 网格区域(grid area) 是一块逻辑意义上的空间, 它包含了一个或多个相邻的网格单元, 由某四条网格线围成. 网格区域能够经过名字定义, 也能够经过网格线定义. 做为元素定位时的参考系, 是一个逻辑意义上的实体, 不会被渲染.
  • 显式网格(explicit grid)和隐式网格(implicit grid), 经过 grid-template-columnsgrid-template-rows 建立出来的轨道, 咱们称它们属于显示网格. 可是这些轨道不必定能容纳全部的网格项目, 浏览器根据网格项目的数量计算出来须要更多的轨道, 就会自动生成新的轨道, 这些自动生成的轨道咱们称它们属于隐式网格.
  • 网格间距(gutter) 经过 grid-row-gapgrid-column-gap (可是如今 W3C 标准中已经修改成经过 row-gap, column-gap 来定义了. 没错, 就是那个列布局中也会用到的 column-gap)定义相邻轨道之间的空白空间. 是一个额外的不占用轨道空间的空白. 对于跨越多个网格单元的网格项目, 不只仅占据单元格的空间, 也占据其中的网格间距的空间. 网格间距只会在显示网格中出现.
  • 剩余空间是我本身发明的词, 即网格容器某方向上的大小(content-box 的 width)减去该方向上全部的网格间距之和剩下的空间, 也即全部行/列的宽度之和.

接下来也会经过具体例子进一步说明这些名词.

新单位 fr 和 repeat(), minmax() 函数

在看具体例子以前先了解下随着 grid 布局一同出现的新单位 fr 以及两个新函数 repeat(), minmax(). 这里只简单地介绍这些内容, 以后会有更具体的说明.

fr

fr 其实就是分配剩余空间的权重, 熟悉 flex-grow 的话能够很容易 get 到这一点.

img107

能够看到, 咱们有一个网格容器, 容器有网格间距, 其中有三个网格项目, 它们的宽度都是 1fr, 因此它们均分了剩余空间.

img108

而 2fr 的话则是这样, 它的工做机制就像 flex-grow 那样.

repeat()

repeat() 函数能够简单地理解为宏展开, 就像 Sass 中的 mixin 那样, 举个例子

grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr 2fr);
复制代码

至关于

grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: 1fr 2fr 1fr 2fr 1fr 2fr;
复制代码

repeat(3, 1fr) / repeat(3, 1fr 2fr) 展开成了 1fr 1fr 1fr / 1fr 2fr 1fr 2fr 1fr 2fr, . 固然, 这里只是简单地这么理解, 以后还有更具体的说明.

minmax()

minmax() 用来指定一个轨道的宽度值, 好比你能够指定一个轨道宽固定为 200px, 你也能够指定一个轨道宽度为 minmax(200px, 1fr), 这样的话, 轨道宽度是弹性的, 最小为 200px, 最大为 1fr.

使用 grid 布局

使用 grid 布局的通常套路能够总结为:

定义网格容器 -- 划分网格单元 -- 根据网格单元定义网格项目

来看一个例子

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
</div>
复制代码
.main {
	width: 300px;
	background: #fff4e6;
	border: 2px solid #f76707;
	border-radius: 5px;
	margin-bottom: 50px;
	display: grid;
}

.item {
	color: #d9480f;
	background: #ffd8a8;
	border: 2px solid #ffa94d;
	border-radius: 5px;
}
.m0 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px 100px;
}
复制代码

img109

咱们先定义了一个网格容器, 而后 grid-template-columns: 1fr 1fr 1fr; 定义了三列, 每列宽为 1fr, grid-template-rows: 50px 100px; 定义了两行, 其中第一行为 50px 高, 第二行为 100px 高. 网格项目默认被放入一个网格单元中, 按照从左往右从上往下排列, 因而获得如图效果. 其中 grid-template-columns: 1fr 1fr 1fr; 也能够写成 grid-template-columns: repeat(3, 1fr);.

显式网格和隐式网格

在上面的例子中, 咱们经过 grid-template-columnsgrid-template-rows 把网格容器划分红了 2x3 个网格单元, 假如咱们只指定一行的话又是怎样?

.m1 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px;
}
复制代码

img110

能够看到, 尽管咱们没有指定两行, 可是网格容器依然被划分红了 2x3 个网格单元, 其中第一行的全部网格单元是咱们经过 grid-template-columnsgrid-template-rows 手动建立的, 咱们把这种手动建立(即宽高都是由开发者定义的)的行和列产生的网格单元称为显式网格(explicit grid), 第二行是浏览器根据网格项目的个数计算自动生成的, 咱们把这种由浏览器自动生成的网格单元为隐式网格(implicit grid).

咱们指定了第一行为 50px 高度, 可是第二行由于是自动生成的, 因此高度默认由浏览器根据内容决定. 那是否是咱们就没办法指定隐式网格的大小了? 也不是, 咱们能够经过 grid-auto-columnsgrid-auto-rows 来指定隐式网格的大小.

.m2 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px;
    grid-auto-rows: 100px;
}
复制代码

img112

能够看到, 第二行又变回了 100px 高.

既然能够自动生成行, 那何时自动生成列? 咱们能够经过 grid-auto-flow 来改变网格项目的排列方向.

.m3 {
	grid-template-columns: 1fr;
	grid-template-rows: repeat(3, 50px);
	grid-auto-flow: column;
}
复制代码

img113

这里咱们指定了三行, 可是只指定了一列, 可是在 grid-auto-flow 的做用下, 本来先从左往右, 再从上往下排列的网格项目变成了先从上往下, 再从左往右排列, 因而第二列属于隐式网格.

到这里, 最基本的 grid 布局方式就比较清楚了, 不过若是 grid 布局仅仅是这些内容, 那前面我也就不用废话这么多了.

以上代码见 demo.

基于网格线的定位

咱们以前说了, grid 布局的通常套路是定义网格容器 -- 划分网格单元 -- 根据网格单元定义网格项目, display: grid; 完成了定义网格容器这步. 经过 grid-template-columns grid-template-rows grid-auto-columns grid-auto-rows 定义了 mxn 个网格单元, 完成了划分网格单元这步. 可是咱们并无根据网格单元定义网格项目, 网格项目是浏览器按照特定顺序自动填充到每一个网格单元中的, 那要怎么完成这一步呢?

最简单的是经过网格线, 还记得以前提到过, 网格线是有本身的编号的, 也能够有本身的名字, 接下来咱们经过编号和名字来完成这最后一步.

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
</div>
复制代码
.m0 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m0 > .item:nth-child(2) {
	grid-row-start: 2;
	grid-row-end: 4;
	grid-column-start: 2;
	grid-column-end: 4;
}
复制代码

img114

咱们定义了 4x5 个网格单元, 从调试工具能够很清楚地看到, 第二个网格项目占据了 2x2 个网格单元, 因此看起来比其余几个网格项目大了很多. 而这里, 其实就是咱们经过网格线指定了第二个网格项目的范围, grid-row-start 指定了网格项目的水平方向的起始边是水平方向编号为 2 的网格线, grid-row-end 指定了水平方向结束边是水平方向编号为 4 的网格线, grid-column-start 指定了垂直方向起始边是垂直方向编号为 2 的网格线, grid-column-end 指定了垂直方向结束边是垂直方向编号为 4 的网格线, 因而第二个网格项目的范围就是 row2, row4, col2, col4 这四条网格线围成范围, 这样一个网格项目就能够占据多个网格单元.

可能已经有人注意到 3 和 4 被放到了 2 的上面, 不由会问 3 和 4 是按照什么规则放的? 这个问题留给以后解释.

对于这个例子, 咱们还能够以缩写的形式, 经过 grid-row grid-column 或者 grid-area 来完成.

.m1 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m1 > .item:nth-child(2) {
	grid-row: 2/4;
	grid-column: 2/4;
}
复制代码

或者

.m2 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m2 > .item:nth-child(2) {
	grid-area: 2/2/4/4;
}
复制代码

其中 grid-area 的顺序是 grid-row-start grid-column-start grid-row-end grid-column-end.

咱们还能够省略 grid-column-endgrid-row-end, 这样的话浏览器默认将它们设置成比 start 编号大 1 的网格线, 即只占一个轨道.

.m1 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m1 > .item:nth-child(2) {
	grid-row-start: 2;
	grid-column-start: 2;
}
复制代码

img115

能够看到, 第二个网格项目只占了一个网格单元.

以上这些网格线相关的属性还能够是负值, 表示倒数第 n 条网格线. eg.

.m4 {
	grid-template-columns: repeat(5, 1fr);
	grid-template-rows: repeat(4, 50px);
}
.m4 > .item:nth-child(2) {
	grid-area: -1/2/4/-1;
}
复制代码

img116

这里 -1 表示倒数第一条网格线, 因此第二个网格项目是 row5, col2, row4, col6 这四条网格线围成的区域.

命名的网格线

前面咱们还说了, 网格线除了有本身的编号, 还能够有本身的名字, 不过编号是浏览器定义的, 而名字是开发者本身定义的. 那怎么定义名字呢? 也很简单, 在定义网格单元的时候能够顺便定义网格线的名字.

<div class="main m5">
	<div class="item">1</div>
</div>
复制代码
.m5 {
	grid-template-columns: [first-start] 1fr [first-end second-start] 1fr [second-end third-start] 1fr [third-end];
	grid-template-rows: [first-start] 50px [first-end second-start] 50px [second-end third-start] 50px [third-end];
}
.m5 > .item {
	grid-area: second-start/second-start/second-end/second-end;
}
复制代码

img117

这里咱们定义了 3x3 个网格单元, 有 3 列, 每列 1fr 的宽度, 1fr 定义了列宽, 两边的 [] 的内容则是轨道两边网格线的名字, 而且一个网格线能够有不止一个名字, 多个名字用空格分隔, 而且不一样方向的网格线可使用相同的名字, 如垂直方向和水平方向都有名为 first-start 的网格线. 以后咱们经过名字而不是编号, 指定了网格项目占据的范围.

相同名字的网格线

不只不一样方向的网格线可使用相同的名字, 其实相同方向的网格线也可使用相同的名字.

.m6 {
	grid-template-columns: [col-start] 1fr [col-start] 1fr [col-start] 1fr [col-start];
	grid-template-rows: [row-start] 50px [row-start] 50px [row-start] 50px [row-start];
}
.m6 > .item {
	grid-area: 1/col-start/span 2/col-start 2;
}
复制代码

img118

这里咱们的列网格线都叫作 col-start, 行网格线都叫作 row-start, 那浏览器要怎么区分它们? 答案就是 col-start 2 这样的值, 表示第二条名为 col-start 的网格线, 也就是编号 2 的网格线. 而若是只写 col-start 不加编号的话则默认是第一条 col-start 的网格线.

这里还有个 span 2, 意思是 start 编号加 2 的网格线, 也就是 1+2=3, 编号 3 的网格线. 因此这里 grid-area: 1/col-start/span 2/col-start 2; 等价于 grid-area: 1/1/3/2;. 对于只指定告终束网格线, 而起始网格线用 span 的话.

grid-column-end: 5;
grid-column-start: span 2;
复制代码

这里的 grid-column-start 应该是 5-2=3, 而不是 5+2=7 了.

以上代码见 demo.

基于网格区域的定位

以前咱们已经知道什么是网格区域, 也接触到了 grid-area, 你会说, 很简单嘛, grid-area 就是一个缩写而已, 指定了四条网格线围成的区域. 不过若是仅仅认为 grid-area 只有这样的功能, 那实在是有点 naive 了.

咱们知道网格线能够有经过名字来引用, 其实网格区域也能够经过名字引用.

<div class="main m0">
	<div class="item header">1</div>
	<div class="item sidebar">2</div>
	<div class="item content">3</div>
	<div class="item footer">4</div>
</div>
复制代码
.m0 {
	grid-template-columns: repeat(9, 1fr);
	grid-auto-rows: minmax(100px, auto);
	grid-template-areas: 
	"hd hd hd hd hd hd hd hd hd"
	"sd sd sd main main main main main main"
	"ft ft ft ft ft ft ft ft ft";
}
.m0 > .header {
	grid-area: hd;
}
.m0 > .footer {
	grid-area: ft;
}
.m0 > .content {
	grid-area: main;
}
.m0 > .sidebar {
	grid-area: sd;
}
复制代码

咱们先定义了 9 列, 而后没有固定行数, 而是指定了隐式网格的行高最小为 100px, 最大由浏览器自动处理. 接着咱们使用了一个 grid-template-areas 的属性, 属性有三个字符串, 映射到三行, 每一个字符串中有 hd 这样的内容, 空格分隔, hd 表示的是一个网格区域的名字, 每一个 hd 对应一个网格单元, 即指定了这个网格单元属于 hd 这个区域, grid-template-areas 其实就定义了一个 3x9 的网格系统.

接着咱们定义了四个类 .header .footer .content .sidebar, 而且经过 grid-area 指定了每一个类占据的区域.

img119

因而咱们经过区域的名字指定了网格项目的范围.

注意到其实这里 grid-template-areas 已经完成了划分网格单元这一操做, 可是它划分的网格单元都是属于隐式网格, 由于它没有指定网格单元的宽高(一个网格单元的宽高都由开发者指定的话才属于显式网格), 因此为了好看点咱们仍是须要 grid-template-columnsgrid-auto-rows.

另外咱们发现, 咱们须要在 grid-template-areas 手写一个矩阵, 而且每一个份量都必须是一个网格区域的名字, 这会致使网格项目恰好占满全部网格单元. 那假如咱们但愿留出一些网格单元做为空白的话怎么办? 就像这样.

img120

咱们能够这么写

.m1 {
	grid-template-columns: repeat(9, 1fr);
	grid-auto-rows: minmax(100px, auto);
	grid-template-areas: 
	"hd hd hd hd hd hd hd hd hd"
	"sd sd sd main main main main main main"
	". . . ft ft ft ft ft ft";
}
.m1 > .header {
	grid-area: hd;
}
.m1 > .footer {
	grid-area: ft;
}
.m1 > .content {
	grid-area: main;
}
.m1 > .sidebar {
	grid-area: sd;
}
复制代码

咱们用 . 替代了本来是网格区域名字的地方, 表示这里的网格单元不属于任何网格区域, 留出空白.

须要注意的是命名的网格区域仍是有它的局限, 那就是没法指定两个网格项目重叠, 若是须要网格项目重叠, 仍是须要经过网格线来实现.

命名网格线定义的命名网格区域

以前咱们的 demo 中, 命名一个网格线都是用 xxx-start, xxx-end 这样的名字, 是否是必定要用 - 链接一个 start 或者 end 呢? 其实也不是, 名字是能够随便起的, 不过按照这种方式起名的话有一个好处, 就是浏览器会自动帮你定义一个 xxx 的网格区域.

举个例子.

<div class="main m2">
	<div class="item content">content</div>
</div>
复制代码
.m2 {
	grid-template-columns: [main-start] 1fr [content-start] 1fr [content-end] 1fr [main-end];
	grid-template-rows: [main-start] 50px [content-start] 50px [content-end] 50px [main-end];
}
.content {
	grid-area: content;
}
复制代码

img121

能够看到, 尽管这里咱们并无定义一个名为 content 的网格区域, 可是当咱们指定 .content 的网格区域为 content 时它被放在了中间, 代表确实存在这么个网格区域, 这个命名的网格区域就是浏览器根据命名的网格线自动建立的. 当以 xxx-start 和 xxx-end 命名网格线时, 若是恰好围成了一个矩形, 则浏览器为这个矩形建立一个名为 xxx 的网格区域.

命名网格区域定义的命名网格线

一样的, 浏览器也会为一个命名的网格区域自动建立命名的网格线.

.m3 {
	grid-template-columns: repeat(9, 1fr);
	grid-auto-rows: minmax(100px, auto);
	grid-template-areas: 
	"hd hd hd hd hd hd hd hd hd"
	"sd sd sd main main main main main main"
	"ft ft ft ft ft ft ft ft ft";
}
.m3 > .header {
	grid-row: hd-start/hd-end;
	grid-column: hd-start/hd-end;
}
.m3 > .sidebar {
	grid-row: sd-start/sd-end;
	grid-column: sd-start/sd-end;
}
.m3 > .content {
	grid-row: main-start/main-end;
	grid-column: main-start/main-end;
}
.m3 > .footer {
	grid-row: ft-start/ft-end;
	grid-column: ft-start/ft-end;
}
复制代码

img122

尽管这里咱们没有定义命名网格线, 可是定义了命名的网格区域, 浏览器自动为这些网格区域生成了对应的命名网格线, 也是以 xxx-start, xxx-end 这样的形式, 因而咱们能够直接经过这些名字引用这些网格线.

以上代码见 demo.

网格间距

网格间距比较简单, 只须要经过 grid-row-gap grid-column-gap 来指定便可.

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
</div>
复制代码
.m0 {
	grid-template-columns: repeat(4, 1fr);
	grid-template-rows: repeat(4, 50px);
	grid-row-gap: 10px;
	grid-column-gap: 20px;
}
.m0 > .item:nth-child(1) {
	grid-area: 1/1/3/3;
}
复制代码

img123

这里咱们经过 grid-row-gap grid-column-gap 指定了行之间的间距为 10px, 列之间的间距为 20px. 须要注意的是, 网格间距并不会致使网格线数量增长.

以上代码见 demo.

Grid 中的自动定位

以前咱们已经注意到, 有些网格项目尽管咱们没指定它们占据的区域, 可是它们仍是会自动按照某种规则进行排列. 如今咱们就来讨论这个规则是怎样运做的.

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
	<div class="item">5</div>
	<div class="item">6</div>
	<div class="item">7</div>
	<div class="item">8</div>
	<div class="item">9</div>
	<div class="item">10</div>
	<div class="item">11</div>
	<div class="item">12</div>
</div>
复制代码
.m0 {
	grid-template-columns: repeat(4, 1fr);
	grid-auto-rows: 50px;
}
.m0 > .item:nth-child(2) {
	grid-area: 2/3/4;
}
.m0 > .item:nth-child(5) {
	grid-area: 1/1/3/3;
}
复制代码

咱们定义了一个 4 列的网格容器, 其中第二个网格项目占据 row2, row4, col3, col4 围成的区域, 第五个网格项目占据 row1, row3, col1, col3 围成的区域. 以后其余元素按照先从左往右, 再从上往下的顺序进行排列. 如图所示.

img125

再考虑这种状况.

.m1 {
	grid-template-columns: repeat(4, 1fr);
	grid-auto-rows: 50px;
}
.m1 > .item:nth-child(2) {
	grid-area: 2/3/4;
}
.m1 > .item:nth-child(5) {
	grid-area: 1/1/3/3;
}
.m1 > .item:nth-child(1) {
	grid-column-end: span 2;
	grid-row-end: span 2;
}
.m1 > .item:nth-child(9) {
	grid-column-end: span 2;
	grid-row-end: span 2;
}
复制代码

依然是 4 列, 12 个网格项目, 其中 2 和 5 的定位没变, 1 和 9 没有指定起始网格线, 只指定告终束网格线, 意味着 1 和 9 要占据 2x2 个网格单元, 可是具体位置由浏览器自动定位. 如图.

img126

咱们把经过网格线或网格区域指定了位置的网格项目称为定位的网格项目, 把没有指定位置而是依靠浏览器自动定位的网格项目称为无定位的网格项目. 设定位的网格项目的集合为 A, 无定位的网格项目的集合为 B, B[i] 的大小为 B[i].x, B[i].y, 总行数为 m, 总列数为 n, 全部网格单元的集合 C 为 mxn 的矩阵, 自动定位的算法能够用伪代码表述为这样

for(i = 0; A不为空; ++i) {
    将A[i]放到对应位置
}
for(j = 0; j < m; ++j) {
    for(k = 0; k < n; ++k) {
        if(B不为空) {
            for(p = 0; p < B[0].x; ++p) {
                for(q = 0; q < B[0].y; ++q) {
                    if(c[j+p][k+q]被占据) {
                        break 2;
                    }
                }
            }
            if(p == B[0].x && q == B[0].y) {
            	cur = B.unshift();
            	把cur放到C[j][k]到C[j+p][k+q]的矩形中
            }
        } else {
            break 2;
        }
    }
}
复制代码

即先逐行扫描, 找到一个空余的网格单元, 再从集合 B 中取一个无定位的网格项目, 看是否可以放下, 不能的话就找下一个空余网格单元, 重复此操做.

因此在这种状况下, 3 没有在 2 的上面, 而是和 2 相邻, 2 和 3 上面留出了空白.

固然咱们也能够把这些空白填上, 只须要经过 grid-auto-flow: dense; 便可, 没错, 就是以前咱们见到的那个改变自动定位流向的 grid-auto-flow.

.m2 {
	grid-template-columns: repeat(4, 1fr);
	grid-auto-rows: 50px;
	grid-auto-flow: dense;
}
.m2 > .item:nth-child(2) {
	grid-area: 2/3/4;
}
.m2 > .item:nth-child(5) {
	grid-area: 1/1/3/3;
}
.m2 > .item:nth-child(1) {
	grid-column-end: span 2;
	grid-row-end: span 2;
}
.m2 > .item:nth-child(9) {
	grid-column-end: span 2;
	grid-row-end: span 2;
}
复制代码

img127

能够看到, 如今 3, 4, 6 把以前 2 上面和边上的空余空间给补上了. 这种状况下的自动定位算法能够描述为

for(j = 0; j < m; ++j) {
    for(k = 0; k < n; ++k) {
        for(i = 0; B不为空;) {
            for(p = 0; p < B[i].x; ++p) {
                for(q = 0; q < B[i].y; ++q) {
                    if(c[j+p][k+q]被占据) {
                        break 2;
                    }
                }
            }
            if(p == B[i].x && q == B[i].y) {
                cur = B.splice(i, 1);
                把cur放到C[j][k]到C[j+p][k+q]的矩形中
                i = 0;
            } else {
                ++i;
            }
        }
    }
}
复制代码

即先逐行扫描, 找到一个空余的网格单元, 再从集合 B 中取一个非定位的网格项目, 看是否可以放下, 若是不能, 则从 B 中取下一个非定位的网格项目, 重复此操做. 这种状况下, 会尽可能保证从上往下没有空白.

注意这里都没有改变自动定位的流向, 即默认是行优先, 先从左往右, 再从上往下, 逐行扫描. 若是改变了自动定位的流向, 即 grid-auto-flow: column; 或者 grid-auto-flow: column dense;, 则算法变为列优先, 即先从上往下, 再从左往右, 逐列扫描.

其实上面隐含了一个小技巧, 就是大部分时候若是咱们但愿指定一个网格项目的大小, 那就得经过网格线或者网格区域, 不可避免地也指定了网格项目的位置, 假如咱们但愿指定一个网格项目的大小, 但又不想指定它的具体位置, 咱们能够省略起始网格线, 经过 span 关键字, 如 grid-template-columns: span 2; 这样, 就指定了一个两列宽的网格项目, 但又没有指定它的具体位置, 而是由浏览器自动定位.

以上代码见 demo.

匿名网格项目

如同匿名块级元素和匿名行内元素同样, 当出现这种状况的时候

<div class="grid">
    test test
    <div class="item">item</div>
    <div class="item">item</div>
</div>
复制代码

未被标签包裹的 test test 内容也会被做为网格项目, 浏览器会为它生成一个匿名网格项目, 匿名网格项目只能被自动定位, 由于咱们没法经过选择器为它们指定位置.

absolute 和 grid

默认状况下, 若是一个网格项目是绝对定位的话, 同其余时候同样, 网格项目脱离文档流, 浏览器不会为它生成隐式网格, 它也不会影响其余网格的定位, 就像它不是网格容器的子元素那样.

<div class="main m0">
	<div class="item">1</div>
	<div class="item">2</div>
	<div class="item">3</div>
	<div class="item">4</div>
</div>
复制代码
.m0 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px 100px;
}
.m0 > .item:nth-child(4) {
	width: 50px;
	height: 50px;
	position: absolute;
	top: 0;
	left: 0;
}
复制代码

img128

能够看到, 4 是绝对定位的, 由于网格容器没有设置任何定位方式, 因此这里它的包含块是根元素而不是网格容器.

而假如网格容器设置了定位, 且绝对定位的元素本来是自动定位的. 以下面例子.

.m1 {
	grid-template-columns: 1fr 1fr 1fr;
	grid-template-rows: 50px 100px;
	position: relative;
}
.m1 > .item:nth-child(4) {
	width: 50px;
	height: 50px;
	position: absolute;
	top: 0;
	left: 0;
}
复制代码

img129

对于设置了绝对定位的自动定位的网格项目, 若是网格容器设置了定位, 则包含块是网格容器.

假如咱们给绝对定位的网格项目设置了网格区域

.m2 {
	grid-template-columns: repeat(4, 1fr);
	grid-template-rows: repeat(4, 50px);
	position: relative;
}
.m2 > .item:nth-child(4) {
	width: 50px;
	height: 50px;
	grid-area: 2/2/4/4;
	position: absolute;
	bottom: 0;
	left: 0;
}
复制代码

img130

则网格项目的包含块是网格区域而不是网格容器. 能够看到, 4 位于网格区域的左下角而不是网格容器的左下角.

img131

这幅图可以很清楚地说明这点.

以上代码见 demo.

grid 中的对齐

同 flexbox 同样, grid 中也有对齐, 依旧是熟悉的属性, 熟悉的味道.

  • align-items
  • align-self
  • align-content
  • justify-self
  • justify-content

在这里, 咱们先纠正两点比较容易引发误会的地方.

  • grid 的主要做用是定位而不是控制网格项目的大小, 尽管它能够用来控制网格项目的大小. 咱们经过给网格项目指定网格线/网格区域, 实际上是指定网格项目在这块区域内定位, 而不是指定网格项目占满这块区域
  • 并非整个网格容器都会被网格轨道占满

实际上, 网格项目的大小是由以上属性来决定的.

这部份内容对于熟悉 flexbox 的人来讲比较简单, 就只贴几个简单的示例好了, 若是不熟悉的话建议先了解 flexbox.

须要注意的是, grid 和 flexbox 不同的是, grid-auto-flow 不会像 flex-direction 那样改变 start 和 end. 好比即便 grid-auto-flow: column; 的状况, align-items 仍是对每行的行内进行上下对齐, 而不会变成对每列列内进行左右对齐. 同理其余属性.

align-items

控制每行中的全部网格项目在行轨道内的对齐方式

align-items: start

img132

align-items: end

img133

align-items: center

img134

align-items: baseline

img135

以上代码见 demo.

align-self

控制单个元素在行轨道内的对齐方式

align-self: end

img136

align-self: center

img137

align-self: baseline

img138

以上代码见 demo.

align-content

控制全部行轨道在网格容器内的对齐方式

.m0 {
	width: 500px;
	height: 400px;
	grid-template-columns: repeat(3, 100px);
	grid-template-rows: repeat(3, 100px);
	align-content: start;
}
.m0 > .item:nth-child(2) {
	font-size: 28px;
}
复制代码

注意到, 全部的列宽之和小于网格容器的宽度, 全部的行高之和小于网格容器的高度.

img139

因而整个网格系统没有占满网格容器, 而是位于网格容器左上角, 这是 align-content: start; 的做用.

align-content: end

img140

align-content: center

img141

align-content: space-around

img142

align-content: space-between

img143

注意, align-content 可能会使得行间距变宽.

以上代码见 demo.

justify-self

相似 align-self, 控制一个网格项目在列轨道上的对齐方式.

justify-self: start

img144

justify-self: end

img145

justify-self: center

img146

以上代码见 demo.

justify-content

相似 align-content, 控制全部列轨道在网格容器内的对齐方式

justify-content: end

img147

justify-content: center

img148

justify-content: space-around

img149

justify-content: space-between

img150

注意, justify-content 可能致使列间距变宽.

以上代码见 demo.

Grid 相关属性

  • grid-template-columns
  • grid-template-rows
  • grid-template-areas
  • grid-template
  • grid-auto-columns
  • grid-auto-rows
  • grid-auto-flow
  • grid-row-start
  • grid-column-start
  • grid-row-end
  • grid-column-end
  • grid-row
  • grid-column
  • grid-area
  • grid-row-gap
  • grid-column-gap
  • grid-gap
  • grid

看上去不少, 不过如今咱们能够把它们划分红几类:

  • 划分网格单元的属性
  • 定位网格项目的属性

grid-template-columns/grid-template-rows

划分网格单元的属性, 做用于网格容器, 用来指定网格的列宽列数以及命名网格线, 默认值 none. eg.

grid-template-columns: 50px 100px;
grid-template-columns: [col-start] 100px;
grid-template-columns: [col-start] 100px [col-end];
grid-template-columns: [col-start] 100px [col-end col-start] 100px [col-end];
grid-template-columns: 50px minmax(30px, 1fr);
复制代码

百分比的值相对于网格容器的 content-box, 若是网格容器的 content-box 大小是由轨道的大小决定的, 则百分比单位被视为 auto.

grid-template-areas

划分网格单元的属性, 做用于网格容器, 用来命名网格区域, 默认值 none. eg.

grid-template-areas: "a a a b" "a a a b";
grid-template-areas: "a a a b" ". . . b";
复制代码

grid-template

划分网格单元的属性, 做用于网格容器, 是 grid-template-columns grid-template-rows grid-template-areas 的缩写.

grid-template: 100px 1fr/50px 1fr; /* rows / columns */
grid-template: [row-line] 100px [row-line] 50px [row-line]/[col-line] 50px [col-line] 50px [col-line];
grid-template: "a a a" "b b b";
grid-template: "a a a" 20px "b b b" 30px;
grid-template: "a a a" 20px "b b b" 30px / 1fr 1fr 1fr;
grid-template: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] / auto 1fr auto;
复制代码

grid-auto-columns/grid-auto-rows

划分网格单元的属性, 做用于网格容器, 用来指定隐式网格的行列宽度.

grid-auto-columns: 1fr;
grid-auto-columns: 1fr 100px; /* 1fr 100px 1fr 100px... */
grid-auto-columns: 1fr 100px 50px; /* 1fr 100px 50px 1fr 100px 50px... */
复制代码

百分比的值相对于网格容器的 content-box, 若是网格容器的 content-box 大小是由轨道的大小决定的, 则百分比单位被视为 auto.

grid-auto-rows / grid-auto-rows 能够不止一个值, 多个值表示交替.

grid-auto-flow

划分网格单元的属性, 做用于网格容器, 用来指定自动定位算法是行优先(先从左往右再从上往下)仍是列优先(先从上往下再从左往右)以及是否填充空余空间. 默认值 row.

grid-auto-flow: row;
grid-auto-flow: column;
grid-auto-flow: row dense;
grid-auto-flow: column dense;
复制代码

grid-column-gap/grid-row-gap

划分网格单元的属性, 做用于网格容器, 用来指定垂直/水平方向的网格间距大小. 默认值 0.

grid-column-gap: 20px;
grid-column-gap: 20%;
复制代码

百分比相对于网格容器的 content-box.

grid-gap

划分网格单元的属性, 做用于网格容器, grid-column-gap grid-row-gap 的缩写.

grid-gap: 10px 20px; /* row col */
复制代码

grid

grid-template-rows grid-template-columns grid-template-areas grid-auto-rows grid-auto-columns grid-auto-flow 的缩写.

grid-row-start/grid-row-end/grid-column-start/grid-column-end

定位网格项目的属性, 做用于网格项目, 指定起始/结束的网格线.

grid-row-start: 3;
grid-row-start: row-start;
grid-row-start: row-start 2;
grid-row-start: span 3;
复制代码

grid-row/grid-column

grid-row-start grid-row-end / grid-column-start grid-column-end 的缩写.

grid-row: 1/3; /* start/end */
grid-column: 2/4;
复制代码

grid-area

定位网格项目的属性, 做用于网格项目, 指定网格项目所属的网格区域.

grid-area: 1/2/3/4; /* row-start/column-start/row-end/column-end */
grid-area: row-start/column-start/row-end/column-end;
grid-area: areaname;
复制代码

几个函数

  • repeat()
  • minmax()
  • fit-content()

fit-content()

适用于任何能够用来定义轨道宽度的属性. 用来指定轨道的宽度, 效果相似于 max-width, 即内容不够给定宽度的话, 按内容宽度, 内容大于给定宽度, 按给定宽度. eg.

grid-template-columns: fit-content(20px);
grid-template-columns: fit-content(20%);
grid-template-columns: fit-content(20vw);
复制代码

其中百分比单位是, 在空间足够时, 相对于网格容器 content-box 对应轴方向的大小, 在空间不够时, 相对于网格容器 content-box 对应轴方向的剩余空间的大小(即 content-box 大小减去全部网格间距的大小).

以上代码见 demo.

minmax()

适用于任何能够用来定义轨道宽度的属性. 用来指定轨道的最小和最大宽度, 效果相似于同时指定了 min-widthmax-width. 带两个参数 min 和 max. eg.

grid-template-columns: minmax(100px, 200px);
grid-template-columns: minmax(100px, 1fr);
grid-template-columns: minmax(100px, 20%);
grid-template-columns: minmax(100px, min-content);
grid-template-columns: minmax(100px, max-content);
grid-template-columns: minmax(100px, auto);
复制代码

几个注意点

  • 若是 min > max, 则忽略 max 的值, 整个 minmax(min, max) 的值视为 min 的值
  • fr 单位能够做为 max, 可是不能做为 min
  • 百分比单位相对于网格容器的 content-box 大小
  • max-content 表示尽量宽, 尽量不换行
  • min-content 表示尽量窄, 达到最大的 inline-box 宽度
  • auto 做为 max 时等同于 max-content, 做为 min 时, 等同于 min-content. 实际测下来, auto 在 max 下的表现和 1fr 同样, 不知道为何

示例代码见 demo

repeat()

适用于任何能够用来定义轨道宽度的属性. 用于重复某一模式生成轨道列表. 效果相似于宏展开或 Sass 的 @mixin. eg.

grid-template-columns: repeat(2, 20%);
grid-template-columns: repeat(2, 1fr); /* 1fr 1fr */
grid-template-columns: repeat(2, [col-start] 1fr); /* [col-start] 1fr [col-start] 1fr */
grid-template-columns: repeat(2, [col-start] 1fr [col-end]); /* [col-start] 1fr [col-end col-start] 1fr [col-end]*/
grid-template-columns: repeat(2, [col1-start] 1fr [col2-start] 3fr); /* [col1-start] 1fr [col2-start] 3fr [col1-start] 1fr [col2-start] 3fr */
grid-template-columns: repeat(2, [col-start] minmax(100px, 1fr) [col-end]);
grid-template-columns: repeat(auto-fill, 100px);
grid-template-columns: repeat(auto-fit, 100px);
复制代码

重点

  • 百分比单位相对于网格容器的 content-box 大小
  • auto-fill 会尽量地让每一个轨道更宽而且尽量在一行(列)中放下更多列(行), 不保证全部轨道占满网格容器. 在每一个轨道宽度肯定的状况下优先确保轨道尽量宽(示例 m1), 在每一个轨道宽度不肯定的状况下, 优先确保放下更多列(示例 m2, m3, m4). (不要看 MDN 的描述!!! 巨他妈难翻译! 看 W3C 的描述 好懂得一比, 其中的 gap 不是指 grid-gap, 而是网格系统没占满网格容器的空余空间都是 gap)
  • auto-fit 会尽量让全部轨道占满网格容器, 而且尽量地让每一个轨道更宽而且尽量在一行(列)中放下更多列(行).
  • 我以为实际场景下, 只要用 auto-fit 就够了...

示例代码见 demo

其余一些细节

  • floatclear 对网格项目是无效的, 可是 float 依然会改变网格项目 display 的计算值(参考 深刻理解float), 网格项目的 display 计算值默认为 blockified(The display value of a grid item is blockified: if the specified display of an in-flow child of an element generating a grid container is an inline-level value, it computes to its block-level equivalent)
  • vertical-align 对网格项目是无效的
  • ::first-line ::first-letter 不能用于网格容器
  • z-index 用来控制网格项目之间的叠加层级
  • order 用来控制自动定位的网格项目的排列顺序
  • 利用媒体查询能够动态地改变列数和行数

参考资料

相关文章
相关标签/搜索