Grid 布局

Grid 布局

Grid布局应该是迄今为止最全面最强大的布局方式。从九宫格到圣杯布局、再到响应式设计,Grid提供了一套完美的解决方案。效果说起来很诱人,但学起来还是需要点儿时间,因为属性超级多,而且每个都有新花样,需要慢慢消化。ps: 多图预警,长篇预警,高能预警。

Introduction

用过BootStrap之类的应该对Grid不陌生,BootStrap3划分栅格主要使用百分比,顺便去看了一下第四代,发现使用的是弹性布局了。

不知道大家看到一个小细节没,打开Chrome开发者工具,选择查看元素,将鼠标移动到html页面,发现浏览器自动给栅格标上了虚线,看下图。

Chrome开发者工具自动标注栅格线

既然是写CSS3相关的属性,按惯例先去Can I Use了解一下浏览器的支持情况,发现主流浏览器都能完美使用了。

Can i use Grid?

Terminology

弹性布局一样,Grid布局也有一些独特的术语,这些务必要理解清楚。

Grid Container

栅格容器是栅格项(Grid Items)的父级元素,也就是那个需要定义display: grid;的那个元素。看下面这个例子,class为grid-container的那个div就是一个栅格容器。

<div class="grid-container">
    <div class="grid-item grid-item-1"></div>
    <div class="grid-item grid-item-2"></div>
    <div class="grid-item grid-item-3">
        <p>我不是grid-container的直接子元素</p>    
    </div>
</div>

Grid Item

栅格项是栅格容器的直接子元素,注意直接这两个字,因此上面代码示例中class为grid-item的元素是栅格项,而里面的p标签则不是。

Grid Line

栅格线是构成网格结构的分界线:垂直的叫做列栅格线(column grid lines),水平的叫做行栅格线(row grid lines),栅格线的概念很重要,后面定义grid-columngrid-row都会直接用到栅格线的概念,如下图黄线即为一条栅格线。

Grid Line

Grid Track

栅格轨道是由两条相邻网格线构成的一个单行单列的区域。注意相邻这个概念,从词法来说,轨道一般都是单条的,如下黄色区域则为一条栅格轨道。

Grid Track

Grid Cell

要注意和Grid Item的区别,一个Grid Cell包含其对应的Grid Item和Grid Item里面的内容,如下图黄色区域即为一个栅格元。

Grid Cell

Grid Area

最后一个术语则是栅格区域,可以说栅格轨道和栅格区域是包含和被包含的关系,如下图的黄色区域就是行栅格线1和3与列栅格线1和3之间圈出来的网格区域。

Grid Area

List of Grid Attributes

List of Grid Container Attributes

  • display

  • grid-template-columns

  • grid-template-rows

  • grid-template-areas

  • grid-template

  • grid-column-gap

  • grid-row-gap

  • grid-gap

  • justify-items

  • align-items

  • justify-content

  • align-content

  • grid-auto-columns

  • grid-auto-rows

  • grid-auto-flow

  • grid

List of Grid Item Attributes

  • grid-column-start

  • grid-column-end

  • grid-row-start

  • grid-row-end

  • grid-column

  • grid-row

  • grid-area

  • justify-self

  • align-self

似曾相识吧,如justify-itemsalign-items在弹性布局中都是核心概念,所以Grid布局则是一个更加全面的布局模式。

ps: 看到这么多属性头都大了,突然想到某考研名师的名言:

やめてよ!!!

BUT! 考虑到梦想、(大误)前途。

嗯,真香...(手动王境泽.jpg)

Grid Container Attributes

首先一一介绍栅格容器的各个属性。

display

display: grid | inline-grid

CSS Grid 布局完全指南(图解 Grid 详细教程)还讲到有一个subgrid的属性值,是在Grid布局里嵌套Grid布局,不过我的WebStorm提示没有这个属性值,那就忽略好了,感觉也没什么卵用。

当容器定义了grid布局之后,容器元素上定义的columnfloatclearvertical-align将失效,注意是容器元素,这些属性不会影响Grid Cell, 亲测,嗯。

grid-template-columns / grid-template-rows

这个属性定义Grid Item的大小,有两个属性值:一个是track-size,另一个是line-name

.grid-container {
    grid-template-columns: <track-size> ... | <line-name> <track-size> ...;
    grid-template-rows: <track-size> ... | <line-name> <track-size> ...;
}

track-size

track-size单位很自由,可以是px、em、rem、vw、vh、百分数、auto等等,特别注意这个auto,看下面这个代码,假设grid-container外面还包着一层grid-wrapper,宽度是500px,那么grid-template-columns属性的auto就会被计算成500-300-100=100px

.grid-wrapper {
  width: 500px;
  .grid-container {
    display: grid;
    grid-template-columns: 300px auto 100px;
  }
}

还有一种情况是grid-container的总宽或总高大于了grid-wrapper的总宽或总高,看下面代码。

.grid-wrapper {
  width: 500px;
  .grid-container {
    display: grid;
    grid-template-columns: 300px 200px 100px;
  }
}

这里的解释是个人的想法,留个坑,看下图。

当grid-container的总宽或总高大于grid-wrapper的总宽或总高

line-name

这个属性值就比较骚气了,可以定义Grid Line的名称,注意要用中括号包起来,第一次见。可以看一个综合示例。

.grid-container {
  display: grid;
  grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end];
  grid-template-rows: [row1-start] 25% [row1-end] 100px [third-line] auto [last-line];
}

一个综合例子

更骚气的是,一条Grid Line还可以有多个名称,看代码,这里的第二条row-grid-line将会有两个名字,分别是row1-endrow2-start

.grid-container {
  display: grid;
  grid-template-rows: [row1-start] 25% [row1-end row2-start] 25%;
}

BUT! 还没骚完...还有一个repeat语法,看下面的代码。

.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 33.33333vw [col]);
  grid-template-rows: repeat(3, 33.33333vh [row]);
}

卧槽,什么概念,三句话搞了一个视口九宫格!想想用传统方式写个九宫格,甚至用弹性布局写个九宫格,而如今...

感动ing...

最后... 还有一个单位... 叫fr...

fr允许你用等分网格容器剩余可用空间来设置Grid Track的大小,看代码。

总宽度是500px,分了三列,其中第一列占了100px,所以后两列占400px,后两列一共是4fr,因此1fr是100px,所以下面的例子等价于grid-template-columns: 100px 300px 100px;

.grid-wrapper {
  width: 500px;
  .grid-container {
    display: grid;
    grid-template-columns: 100px 3fr 1fr;
    grid-template-rows: repeat(3, 100px [row]);
  }
}

蛤?你以为这样就完了?

Naive!

下面要讲到一个属性叫grid-column-gap / grid-row-gap,这个东西很流弊,它用来设置每个Grid Item的间距。

设个间距有什么流弊的?margin不就得了?

但是考虑一个常见的场景,看代码。这是一个常见的navbar,要使用margin-right分隔的话,需要把最后一个limargin-right设为0.

<nav>
    <ul>
        <li>home</li>
        <li>blog</li>
        <li>music</li>
        <li>photo</li>
        <li>about</li>
    </ul>
</nav>

li {
  list-style-type: none;
  display: inline-block;
  margin-right: 10px;
  &:last-child {
    margin-right: 0;
  }
}

BUT!grid-column-gap / grid-row-gap只会在 列/行 之间创建间距,两侧不会有这个间距。看代码。

ul {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-column-gap: 10px;
  li {
    list-style-type: none;
    display: inline-block;
  }
}

当然这个例子不太好,目前实在也想不出太好的例子来,总之就是grid-column-gap / grid-row-gap可以避免左右边界也给创建间隔。

grid-column-gap / grid-row-gapfr有毛线关系?看下面这个例子。假设总宽度为500px,现在列分成了5份,每份占20%.

当没有间隔时,正好每个Grid Item的宽为100px

但有了10px的列间隔后,看下面的图,会发现每个Grid Item的宽仍为100px,但右边溢出了一部分。

.grid-wrapper {
  width: 500px;
  .grid-container {
    display: grid;
    grid-template-columns: repeat(5, 20%);
    grid-template-rows: repeat(2, 100px [row]);
    grid-column-gap: 10px;
}

右边溢出了一部分

当然你可以考虑使用计算属性,看代码。

.grid-wrapper {
  width: 500px;
  .grid-container {
    display: grid;
    grid-template-columns: repeat(5, calc((500px - 10px * 4) / 5));
    grid-template-rows: repeat(2, 100px [row]);
    grid-column-gap: 10px;
}

但是还是很麻烦,所以fr就派上了用场(卧槽前面居然这么多铺垫),以列这个方向为例,fr是总宽度减去固定Grid Track的宽度,如果有间隔,再减去间隔的总宽度,最后再去平分剩余宽度,看代码。完美解决。

.grid-wrapper {
  width: 500px;
  .grid-container {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    grid-template-rows: repeat(2, 100px [row]);
    grid-column-gap: 10px;
}

grid-template-areas

这是用来定义模版区域的一个属性,有三个属性值,分别是:

  • <grid-area-name>:由网格项的 grid-area 指定的网格区域名称
  • .(点号) :代表一个空的网格单元
  • none:不定义网格区域

还是直接看例子。

.grid-item-1 {
  grid-area: header;
}
.grid-item-2 {
  grid-area: main;
}
.grid-item-3 {
  grid-area: aside;
}
.grid-item-4 {
  grid-area: footer;
}
.grid-container {
  display: grid;
  grid-template-columns: 50px 50px 50px 50px;
  grid-template-rows: auto;
  grid-template-areas:
          "header header header header"
          "main main . aside"
          "footer footer footer footer";
}

整个栅格系统分了四列,每列宽50px,第一行四个全被定义成了header;第二行从左右到右,main占了列,第三列未定义名称,所以用.来占位,最后一个则被定义为aside;最后一行则被footer所占据。

grid-template-areas示例示意图

看到这里,就有种模块化的味道了,比如在class为grid-item-1的部分去定义header的相关属性;在class为grid-item-4的部分去定义footer的相关属性。

放一个我写的例子。

grid-template

这个属性是grid-template-rowsgrid-template-columnsgrid-template-areas的合体版。

私以为本来就很复杂了,合起来更麻烦了,而且grid-template不会重置隐式栅格属性,即grid-auto-columngrid-auto-rowsgrid-auto-flow,而且auto又很常见,所以少用为妙,而且下面还有跟好用的办法。

grid-column-gap / grid-row-gap

不多说,上面已经介绍到了。这里重复一下,grid-column-gap / grid-row-gap只会在 列/行 之间创建间距,两侧不会有这个间距。

grid-gap

这是grid-column-gapgrid-row-gap的合体写法,具体语法如下:

.grid-container {
    grid-gap: <grid-row-gap> <grid-column-gap>;
}

看一个特殊情况:

 .grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 100px [row]);
    grid-gap: 10px;
}

grid-gap只写了一个值,这个值会被认作是grid-column-gap的属性值,这个时候相当于grid-row-gap没有定义,因此grid-row-gap也会被赋给这个值,相当于grid-gap: 10px 10px;.

因此,如果只想要列间距,就写成grid-gap: 0 10px;即可。

justify-items

注:这个属性的细节,包括下面的align-items就不详细写了,这一块和我以前写过的一篇文章弹性布局一模一样,那里对每个属性值都做了分析,这里只是简单贴一下介绍和属性值list.

justify-items为沿着行轴线(row axis) 对齐网格项(grid items) 内的内容。

  • start:将内容对齐到网格区域(grid area)的左侧

  • end:将内容对齐到网格区域的右侧

  • center:将内容对齐到网格区域的中间(水平居中)

  • stretch:填满网格区域宽度(默认值)

align-items

align-items为沿着列轴线(column axis) 对齐网格项(grid items) 内的内容。

  • tart:将内容对齐到网格区域(grid area)的顶部

  • end:将内容对齐到网格区域的底部

  • center:将内容对齐到网格区域的中间(垂直居中)

  • stretch:填满网格区域高度(默认值)

justify-content

此属性沿着行轴线(row axis) 对齐网格。

  • start:将网格对齐到 网格容器(grid container) 的左边

  • end:将网格对齐到 网格容器 的右边

  • center:将网格对齐到 网格容器 的中间(水平居中)

  • stretch:调整 网格项(grid items) 的宽度,允许该网格填充满整个 网格容器 的宽度

  • space-around:在每个网格项之间放置一个均匀的空间,左右两端放置一半的空间

  • space-between:在每个网格项之间放置一个均匀的空间,左右两端没有空间

  • space-evenly:在每个栅格项目之间放置一个均匀的空间,左右两端放置一个均匀的空间

align-content

此属性沿着列轴线(column axis) 对齐网格。

  • start:将网格对齐到 网格容器(grid container) 的顶部

  • end:将网格对齐到 网格容器 的底部

  • center:将网格对齐到 网格容器 的中间(垂直居中)

  • stretch:调整 网格项(grid items) 的高度,允许该网格填充满整个 网格容器 的高度

  • space-around:在每个网格项之间放置一个均匀的空间,上下两端放置一半的空间

  • space-between:在每个网格项之间放置一个均匀的空间,上下两端没有空间

  • space-evenly:在每个栅格项目之间放置一个均匀的空间,上下两端放置一个均匀的空间

grid-auto-columns / grid-auto-rows

此属性用于自动生成grid-template-rowsgrid-template-columns创建的Grid Track。

有些绕,看例子,很显然会生成下图这样一个栅格系统。

.grid-container {
    display: grid;
    grid-template-columns: 60px 60px;
    grid-template-rows: 90px 90px;
}

显式栅格

这里暂时用一下Grid Items的语法grid-columngrid-row,以区域2为例,它在垂直方向由第一根线第二根线围成;在水平方向由第二根线第三根线围成。因此区域2可以用一下代码表示:

.area-2{
    grid-column: 1 / 2;
    grid-row: 2 / 3;
}

提前了解一下grid-column和grid-row

回到正题,看下面代码,item-a是在真正的栅格系统里,而item-b不再原栅格系统。

.item-a {
    grid-column: 1 / 2;
    grid-row: 2 / 3;
}
.item-b {
    grid-column: 5 / 6;
    grid-row: 2 / 3;
}

item-b不在正常的栅格系统里

因此就可以用grid-auto-columnsgrid-auto-rows来定义隐式创建的Grid Track的大小。私以为这种出格的事情,还是不要去做的好。

grid

grid是在一个声明中设置所有以下属性的简写:grid-template-rows, grid-template-columns, grid-template-areas, grid-auto-rows, grid-auto-columns, 和 grid-auto-flow

合起来写还是有些头大,我还是分开写(手动债见.jpg)

Grid Items Attributes

呼~终于把Grid Container的属性全写完了,下面再写Grid Item的。

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

上面有简单介绍过grid-columngrid-row,其实这四个属性就是这两种分开写的形式。因为合着写要方便很多,所以这里一笔带过,直接看语法。

.grid-item {
    grid-column-start: <number> | <name> | span <number> | span <name> | auto
    grid-column-end: <number> | <name> | span <number> | span <name> | auto
    grid-row-start: <number> | <name> | span <number> | span <name> | auto
    grid-row-end: <number> | <name> | span <number> | span <name> | auto
}

grid-column/ grid-row

这里还是要单拎出来再次解释一下,因为有一些好玩的东西。

<div class="grid-wrapper">
  <main class="grid-container">
    <section class="grid-item grid-item-1">1</section>
    <section class="grid-item grid-item-2">2</section>
    <section class="grid-item grid-item-3">3</section>
    <section class="grid-item grid-item-4">4</section>
    <section class="grid-item grid-item-5">5</section>
    <section class="grid-item grid-item-6">6</section>
    <section class="grid-item grid-item-7">7</section>
    <section class="grid-item grid-item-8">8</section>
    <section class="grid-item grid-item-9">9</section>
  </main>
</div>

.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: [row-1] 100px [row-2] 100px [row-3] 100px;
  .grid-item-1 {
    grid-column: 1 / span 2;
    grid-row: row-2 / 4;
  }
}

先看grid-columnspan就是该网格项将跨越所提供的网格轨道数量,所以这句等价于grid-column: 1 / 3;

再看grid-row,我们在上面定义的第二条row的名称就叫做row-2,因此row-2就指代2,所以这句等价于grid-row: 2 / 4; 所以这就是定义Grid Line的名称的意义。

比起grid-template-areas的写法,我觉得这个更加方便,因为在HTML中已经做了语义化,再用grid-template-areas定义一遍语义不见得是最好的选择。

在线代码在下面:

grid-area

grid-area为网格项提供一个名称,以便可以被使用网格容器grid-template-areas属性创建的模板进行引用。 另外,这个属性可以用作grid-row-start + grid-column-start + grid-row-end + grid-column-end的缩写。

这里就说一下缩写好了,看代码和图片。

.grid-item-d {
    grid-area: 1 / col4-start / last-line / 6
}

grid-area

justify-self

justify-self是沿着行轴线(row axis) 对齐网格项内的内容。

这又回到了和弹性布局类似的地方,照例写一下所有属性值:

  • start:将内容对齐到网格区域的左侧

  • end:将内容对齐到网格区域的右侧

  • center:将内容对齐到网格区域的中间(水平居中)

  • stretch:填充整个网格区域的宽度(这是默认值)

align-self

align-self是沿着列轴线(column axis) 对齐网格项内的内容。

  • start:将内容对齐到网格区域的顶部

  • end:将内容对齐到网格区域的底部

  • center:将内容对齐到网格区域的中间(垂直居中)

  • stretch:填充整个网格区域的高度(这是默认值)

References

CSS Grid 布局完全指南(图解 Grid 详细教程)

CSS 新的长度单位 fr 你知道么?

注:文章很多图片来自CSS Grid 布局完全指南(图解 Grid 详细教程),如有侵权,将于联系后删除。

Summarize

终于写完了,个人觉得Grid布局更像是一种全局性质的布局方案,一些小的部件如果用的话反倒可能会像Table系的布局一样被限制住。

当然优点显而易见,比如让grid-template-columnsgrid-template-rows使用百分比或视口单位,做响应式开发可谓是方便了太多。

以上、よろしく。

你可能不知道的 CSS3 Animation

NEXT POST

你可能不知道的 CSS3 Animation