上一节的小试牛刀介绍了 grid 的一些基本术语及如何利用网格系统做个简单的 WIN UI 布局。这节将深入挖掘 grid 宝库,继而登堂入室。

Grid 属性指南

Grid 属性分为 container 和 items 上的属性。整个属性有很多,为了方便理解,这里将按照其用途分为 5 个类,分别为:划分网格、自动分配、分布对齐、间距、items 定位。如下图(属性背景色与分类文字颜色对应):

image.png

下面将按照这 5 大分类详细介绍各个属性。

划分网格

该分类属性有 grid-template-rowsgrid-template-columnsgrid-template-areasgrid-template。其中 grid-template 为前面三个的缩写。

grid-template-rowsgrid-template-columns

grid-template-rowsgrid-template-columns,分别用来定义网格的行和列。如定义一个两行三列的网格:

  1. .grid {
  2. display: grid;
  3. grid-template-columns: 100px 100px 100px; /* 每个值表示每列的宽度,这里表示三列,每列的宽度为 100px */
  4. grid-template-rows: 60px 60px; /* 每个值表示每行的高度,这里表示两行,每行的高度为 60px */
  5. }

查看 demo

列宽和行高的取值有以下几种:

  • none | <length> | <percentage> | auto
  • <flex>
  • max-content | min-content
  • minmax(min, max)
  • fit-content( [ <length> | <percentage> ] )
  • repeat( [ <positive-integer> | auto-fill | auto-fit ] , <track-list> )

none | <length> | <percentage> | auto 这些常规的就不做介绍了,下面深入了解下其他取值的具体应用。

<flex>

grid 中的 <flex> 通过 fr 单位来表示, 1fr 表示 grid container 中剩余宽度的一份(和 flexbox 的剩余单位分配差不多)。

如应用在 grid-template-columns 表示分配的是 inline axis 的剩余空间,相应的应用在 grid-template-rows 上表示分配的是 block axis 的剩余空间。

.grid {
  /* 定义三列
  * 第一列宽度为剩余宽度的三分之一 = 1fr/(1fr + 2fr)
  * 第二列宽度为剩余宽度的三分之二 = 2fr/(1fr + 2fr)
  * 第三列宽度为 100px
  * 总的剩余宽度为 = 100%(container 宽度) - 100px(已设置的宽度)
  */
  grid-template-columns: 1fr 2fr 100px;
}

查看 demo

max-content | min-content

max-content | min-content 表示使用最大的内容宽度(行内元素不折行)或最小的内容宽度(行内元素自动折行)。下面通过实例来解释下它们的区别。

如下两段文字,第二段文字是一样的。第一列值为 max-content 第二列值为 min-content。可以看到第一列行内元素不折行,形成最大的内容宽度。第二列行内元素会自动折行,但是会取最大的不可换行的长度(图片中为 representing 单词的长度)为最小的内容宽度。

image.png

查看 demo

minmax(min, max)

minmax(min, max) 表示一个取值范围,最小最大是多少。其 min 和 max 的取值为:<length> | <percentage> | auto | <flex> | max-content | min-content。下面是一些例子:

minmax(200px, 1fr)
minmax(auto, 300px)
minmax(min-content, 400px)
minmax(max-content, auto)

fit-content( [ <length> | <percentage> ] )

该函数会根据计算公式 min(maximum size, max(minimum size, argument)) 取得最终的值。

其中 maximum size 就是上面的 max-content,minimum size 对应的就是 min-content

这样该计算公式会先取 min-content 和设置值的最大值,然后再取该最大值与 max-content 的最小值。

下面通过实例来说明:

grid-template-columns: fit-content(400px) fit-content(400px) fit-content(30px);

如下图,由于第一列和第二列的 400px 都大于 min-content,所以就是从max-content400px 两者选最小值,于是第一列为 max-content,而第二列为 400px。而第三列由于 30px 小于 min-content,所以在 max-contentmin-content 中取最小值为 min-content

image.png

查看 demo

repeat( [ <positive-integer> | auto-fill | auto-fit ] , <track-list> )

对于连续有规律的重复列或行,还可以使用 grid 中新增的 repeat 函数来表示重复,如上面的两行三列写成 repeat 如下:

.grid {
  grid-template-rows: repeat(3, 100px);
  grid-template-columns: repeat(2, 60px);
}

当然还可以设置多行或多列重复:

.grid {
  /* 两列(100px 和 200px)一起重复 2 次 */
  grid-template-rows: repeat(2, 100px 200px);
}

效果图如下:

image.png

查看 demo

auto-fill vs auto-fit

第一个值除了设置正整数之外,还可以使用 auto-fill | auto-fit 这两个关键词。简单来说,这两个值表示尽可能多的重复 items 个数,其关键区别在于:如何分配 container 的剩余长度。所以当 items 不具备弹性设计或 container 不具备剩余长度时,这两个其实是一样的作用。

首先来看一个固定宽度的,既然是固定宽度也就无所谓剩余宽度的分配了,所以两者表现一致:

.grid--1 {
  /* inline 轴显示的最多 items 个数 = 取整(container 宽度 / 200px) */
  grid-template-columns: repeat(auto-fill, 200px);
}
.grid--2 {
  /* inline 轴显示的最多 items 个数 = 取整(container 宽度 / 200px) */
  grid-template-columns: repeat(auto-fit, 200px);
}

查看 demo(可调整视窗宽度,查看 inline 轴上的最多可容纳 items 个数)

接下来看一个存在剩余空间分配的情况:

.grid--1 {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

查看 demo(可调整视窗宽度查看如何进行剩余空间分配)

当 items 的个数乘以 minmax 函数中的最小值,小于 container 宽度时,container 将会出现剩余空间。当这剩余空间大于 minmax 函数中的最小值,auto-fill 表现为尽可能多的先生成 grid cell,然后实在生成不了 cell 的剩余空间再分掉(最后 items 个数小于 cell 的个数),而 auto-fit 则表现为直接将剩余空间分掉分到各个 items 上(最后其 items 个数等于 cell 的个数)。

所以总的来说,auto-fillauto-fit 的区别就在于如何分配剩余空间

到此为止,划分网格这块已经差不多完成了,剩余命名网线定义 areas 这块将在 items 定位中继续详解。

自动分配

自动分配总共涉及三个属性,分别为: grid-auto-flowgrid-auto-columnsgrid-auto-rows。其中第一个用于决定 items 的排列方向,第二个和第三个用于当 items 个数超过 cell 个数的自动大小。

grid-auto-flow

其取值为:[ row | column ] || dense。默认为 row

  • row 表示自动排列的 items 按照先从左到右,再从上到下一个个对应到 grid cell 中。
  • column 表示自动排列的 items 按照先从上到下,再从左到右一个个对应到 grid cell 中。
  • dense 表示是否密集填充,如果非密集填充会按照顺序一个个填充,不会检查前面是否还有遗漏的空 grid cell,而设置为密集填充后会检查并填充前面遗漏的空 grid cell。

image.png

查看 demo

grid-auto-columns & grid-auto-rows

grid-auto-columnsgrid-auto-rows 表示设置自动列或行。在有些时候,我们其实无法确定 items 会有多少个,那么就不太清楚最后到底会生成多少行或列,这时候就需要用到自动扩展的行或列。如每次搜索结果的多少变化,响应式时候改变视窗大小导致行或列的变化, 或一些自定义的 dashboard 面板。

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
  grid-auto-rows: 100px; /* 自动扩展行 */
  grid-gap: 5px;
}

查看 demo(改变视窗宽度,自动扩展行)

网格间距

网格间距总共涉及三个属性,分别为: grid-column-gapgrid-row-gapgrid-gap。其中 grid-gapgrid-column-gapgrid-row-gap 的简写,分别表示列之间的间距和行之间的间距,其语法为:grid-gap: <grid-column-gap> <grid-row-gap>

.grid {
  grid-gap: 15px 30px;
}

/* 等同于 */
.grid {
  grid-column-gap: 15px;
  grid-row-gap: 30px;
}

如只有一个值,则行列间距相等,如 gird-gap: 20px;

Items 定位

在上一节的小试牛刀中,我们已经介绍了 Items 定位相关的属性(除了 order 之外)。这里我们再简单回顾下:grid 中的每个 items 都可以借助四条线来决定定位。这四条线分别是两条横线和两条竖线,其形成的交叉区域就是定位的位置。默认的话,一个 item 占据一个 cell,而如果一个 item 占据多个 cell,就需要进行额外的定位处理,如下图:

image.png

命名 lines

对于一个网格来说,如要实现 n行 * m列的网格,则需要 n+1 条水平线,m+1 条垂直线。默认通过数字就可以去确定是第几条线,而为了方便理解或使用,我们还可以对 grid lines 进行命名。

每条线我们都可以起一个名字或多个名字,但是每个行的名字要保持在所有行中唯一,同样列也是如此。不过行可以跟列用一样的名字。如下图第三行起了两个名字,分别是 row-end、last;而第五列起了一个名字为 last:

image.png

既然知道了可以起名字,那么起名字用的是什么属性呢?这就要回到前面的划分网格的属性了。

如上图中的网线名字,定义网格的时候可以设置如下:

.grid {
  display: grid;
  /* 行或列的值其实是插在两个相邻的 grid line 之间的 */
  /* 如:grid-template-columns: [1] 60px [2] 60px [3] 60px [4] 60px [5]; */
  grid-template-columns: 60px 60px [middle] 60px 60px [last];
  grid-template-rows: [top row-start first] 60px [second] 60px [row-end last];
}

网线行或列的命名格式为:[line1 ...] width1 [line2 ...] width2 [lineN ...] widthN [line(N+1) ...]

其中中括号表示网线的名字,名字可以有一个或多个,以空格隔开。widthN 表示第 N 行或列的宽度。

这样在定位 items 的时候,就可以使用刚才的命名来定位了。如设置第二个的定位在:

.item:nth-of-type(2) {
  grid-row: row-start / row-end;
  grid-column: middle / last;
}

查看 demo

对于 grid-rowgrid-column,除了指定第几条线或命名的线条之外,还可以使用 auto(表示自动分配)和 span n (表示跨 n 行或列)来进行定位。

grid-row: 1 / 3 就等同于 grid-row: 1 / span 2。如何使用请查看 demo

命名 areas

除了对 lines 命名外,还可以命名 areas。先简单回顾下如何定义一个 area。

由任意两条横向网格线和两条纵向网格线所组成的区域都是 grid area(可以对其命名,以方便使用),其可能由一个或多个 grid cell 组成,如下图:

image.png

而对 areas 进行命名,需要用到的正是划分网格里面的第三个属性 grid-template-areas

如将上图的那个 area 命名为 music:

.grid {
  display: grid;
  grid-template-columns: repeat(4, 100px);
  grid-template-rows: repeat(2, 100px);
  /* grid-template-areas 命名规则为:
  * 每行用引号包裹,里面给每个 cell 命名,空格隔开,不命名的可以使用 `.` 表示。
  * 行与行之间空格隔开
  */
  grid-template-areas: ". . music music" ". . music music";
}

grid-template-areas 其实就是对每个 cell 进行命名,然后命名相同的 cells 会自动合成为一个矩形 area

area 命名完了,items 定位就可以通过 grid-area 来使用了。

.item {
  /* 使用命名的 music 区域 */
  grid-area: music;
}

更详细的一个 demo 可参考: MDN grid template areas

到此划分网格的属性就只剩下缩写的 grid-template 了。其缩写规则为:grid-template-areas grid-template-rows / grid-template-columns。这里就不多做介绍,以下是 MDN 的一些使用例子:

/* Keyword value */
grid-template: none;

/* grid-template-rows / grid-template-columns values */
grid-template: 100px 1fr / 50px 1fr;
grid-template: auto 1fr / auto 1fr auto;
grid-template: [linename] 100px / [columnname1] 30% [columnname2] 70%;
grid-template: fit-content(100px) / fit-content(40%);

/* grid-template-areas grid-template-rows / grid-template-columns values */
grid-template: "a a a"
               "b b b";
grid-template: "a a a" 20%
               "b b b" auto;
grid-template: [header-top] "a a a"     [header-bottom]
                 [main-top] "b b b" 1fr [main-bottom]
                            / auto 1fr auto;

定位总结

定位一个 item,既可以使用 grid-area 属性表示占位这个区域,也可以使用 grid-rowgrid-column 属性表示从哪行/列起始到哪行/列结束。

默认 grid lines 会自动使用数字表示第几个行/列线。为了理解,还可以在划分网格的时候给 lines 及 cells 进行命名,命名相同的 cells 会自动合成为一个矩形 area(如果形成的是非矩形 area,说明命名错误),grid-area 就可以直接使用该名字表示占位该矩形 area。

分布对齐

轴线

先回顾下上一节介绍的轴线。如下图,分布对齐正是基于这些轴线进行的:

image.png

注:下文的块轴表示 block/column axis,行内轴表示 inline/row axis。

grid items 的对齐

在块轴上可以通过设置 align-itemsalign-self 来控制 grid items 的对齐,而在行内轴上可以通过设置 justify-itemsjustify-self 来控制 grid items 的对齐。其中 align-itemsjustify-items 用于 grid container 控制所有 items 的对齐,而 align-selfjustify-self 用于 grid items 上控制单个 item 的对齐。

四个属性的取值均为:auto | normal | start | end | center | stretch |baseline | first baseline | last baseline

其对齐的标准都是基于该 item 所占的区域,如下图 (chrome 开启调试,每个 cell 区域都有虚线边框,除 baseline 相关的对齐,其余均可以以这些虚线为对齐参考线)。如果不设置任何对齐,则两个轴上都使用默认值 stretch 平铺整个区域大小(item 1 和 item 4);如果只设置一个轴方向的对齐,则另一个轴取默认值 stretch(item 2,行内轴 center );如果两个轴上都设置对齐方式,则实际大小为内容大小或设置的宽高(item 3,块轴与行内轴均为 center)。

image.png

更多 demo 实例可见:Grid items 的对齐

grid tracks 的分布对齐

当 gird tracks 的宽或高小于 grid container 的宽或高,就涉及到 tracks 的分布对齐。如下图,整个灰色区域为 grid container 的大小,但是划分的网格大小为四个蓝色区域,这样就可以设置 tracks 的分布对齐了:

image.png

相关属性为 justify-conentalign-content。其中 justify-conent 表示在行内轴上的分布,而 align-content 表示在块轴上的分布。这两个属性的取值均为:normal | start | end | center | stretch | space-around | space-between | space-evenly | baseline | first baseline | last baseline

这里面的很多值在 flexbox 都已经讨论过,唯有 space-evenly flebox 还不支持,其效果如下图(设置行内轴的 justify-conent: space-evenly),将 items 之间的距离与每行/列的首尾两个 items 到 container 的距离保持一致:

image.png

更多 demo 实例可见:grid track 的分布对齐

items 挖掘

items 的 order

order 用来改变 items 的渲染出现顺序,可用于 SEO。值越小,顺序越靠前(跟 flexbox items 的 order 一样)。取值为:<integer>,默认为 0

如下图,item3 的 order-1, item 2 的 order1

image.png

查看 demo

items 的 margin

items 的 margin 是基于 grid area 来计算的,如下图,item 2 的 margin10px,item 3 的 marginauto

image.png

查看 demo

items 的绝对定位

Grid items 的绝对定位有一个比较特殊的情况:当 grid container 为容器块(如设置 position 为非 static),且 item 为非自动定位(auto-placement) 的绝对定位,其定位以该 item 的 area 为参考。

如下图,Grid container 为容器块,item 2 为绝对定位且设置了 grid-area,所以定位参考为其 area;而 item 5 为自动定位,所以其定位参考为 grid container:

image.png

查看 demo

items 的 z-index

除了上述绝对定位的特例外,items 还可以直接设置 z-index,并不需要设置相对定位或绝对定位这个前提。

如下图,item 2 和 item 3 有一块相交的区域,默认的话 item 3 应该位于上面,但是可以通过设置了 item 2 的 z-index,让其位于 item 3 之上:

image.png

查看 demo

Gird 兼容

根据最新的 can i use 数据(2019-3-18),各主流浏览器都已经实现了标准版的 grid layout。如下图(不同的浏览器可能会有一些 bugs,具体参考兼容表下面的 know issues。):

image.png

除了标准版之外,IE10-11 和 Edge 15- 还实现了一个老版本的 grid layout。老版本除了属性不一样外,还存在一些能力不支持,如 grid-template 缩写,grid-area 等。如果不幸需要兼容老版本,也不要慌,使用 autoprefix 开启 grid 即可。

对于其他不支持的那就真没招了,用其他方法去实现吧。如果浏览器支持 @support 则可以使用该方法来判断是否支持 grid layout,然后写对应的样式。如果浏览器连 @support 都不支持,那只好通过 JS 判断浏览器是否支持 grid layout,如果支持则为根元素加一个 class,不支持则添加另一个 class,这样就可以支持的写 grid,不支持就写普通的了。

总结

总得来说,grid layout 涉及到的属性很多,但是将其分门别类的整理下,整个思路就非常清晰了。再复杂也不过先划分网格,然后给 items 定位,再做一些额外处理。现在扎实的理论基础有了,下节将继续应用实战。

参考资料