JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

前言

在上篇——JavaScript五十问——对比来说CSS的Grid与FlexBox(上篇),我介绍了Flex的属性与使用,今天我们来总结一下Grid的具体使用方法,最后会结合Flex与Grid布局讲一讲二者的联系与不同。

需要注意得是,Grid布局与我们之前所熟悉的css布局思路有很大的不同,阅读这篇文章之前,需要把我们平时对css的刻板印象抛弃掉,准备接受知识的洗礼吧,少年!

Grid

与 Flex 相同,Grid 也分为容器与元素两个概念;在一个 html 标签中添加样式:display:grid或者display:inline-grid,即构建了一个 Grid 的容器,里面的 dom 元素即为 Grid 元素。同样,Grid 也分为两类属性,分别装载在容器与元素上,下面一一说明。

HTML结构说明

以下所有例子均基于或扩展于下面的HTML结构:

<style>
    .container{
      width:500px;
      background-color:#999;
    }
    .item{
      width: 50px;
      background-color:#567;
      color:#fff;
    }
  </style>
 <div class="container">
    <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>

Grid 基本概念介绍

网格

Grid容器里面有网格一系列的概念;听着唬人,但是结合图很容易理解。

网格线

水平方向有垂直方向的线段即为网格线
JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

网格轨道

两个相邻的平行网格线之间的区域就是网格轨道
JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

网格单元

四个相邻边组成的区域就是网格单元。
JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

fr

fr是Grid中特有表示尺寸的单位,是分数——fraction的缩写,假设我们现在有四个grid元素,每个元素的宽度都是1fr,那么每个元素的实际宽度就是总宽度的1/4。

fr也可以跟%,px 共同使用,他的计算规则就是刨去px与%的剩余空间作为fr分配空间,所有fr相加之和作为分母,分子为每个元素对应的fr的值。(当然,在Grid语境下,我们是不需要设置width属性的,这样说是为了让大家容易理解)。

这样说来好像fr仅仅是%的另外一种写法,随着我的介绍,你就会发现fr优于%的地方。

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)
接下来,我还是以脑图为思路介绍Grid的各个属性。

容器属性

grid-template

grid-tempalte是三个属性的简写,这三个属性都是描述整块区域即多个网格单元的属性。

grid-template-rows

grid-template-rows是描述横向单元轨道属性的。可以试想一下,我们在一个Grid容器中,关于这个属性,我们关心的是什么呢?无非就是这个容器中有多少行,每行的高度;所以,这个属性就是让我们定义这些值得。先来看语法:
grid-template-rows: <track-size> | <line-name> <track-size> ...;
这个属性除了可以定义轨道尺寸和个数之外,还允许我们定义两个轨道之间的网格线的名称,至于他的作用,我们先按下不表,先来看这个属性是怎样定义每个轨道的尺寸和轨道个数的。
先来看一个例子:

.container{
    grid-template-rows:200px auto 1fr 1fr 20%;
    grid-row-gap:10px;//定义轨道之间的间距
}

grid-template-rows定义了五个值,表示Grid容器里面有五行,可以使用任意的定义尺寸的方式,效果如下

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

其中auto值就表示元素的实际占用大小。
Grid分配空间首先计算除了fr对应轨道的尺寸,然后将剩余尺寸按照比例分配给fr加持的元素。
以上,我们在Grid容器里定义了五行容器轨道,当我们定义轨道过多时,可以使用repeat函数来减少我们的工作量,语法为:
grid-template-rows:repeat(n, size)
例子:

.container{
    grid-template-rows:repeat(5,1fr);
    grid-row-gap:10px;
}

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)
上面就定义了五个网格轨道,每个轨道的高度是Grid容器高度的1/5。

grid-template-columns

grid-template-columns 与 grid-template-rows使用方法是一致的,这两个属性共同作用于Grid容器,相当于把Grid容器分割为m*n个子区域。
例子:

.container{
    grid-template-rows:repeat(3,1fr);
    grid-template-columns: repeat(2, 1fr)

上面这个例子就会得到六个均等分的子区域。

通过上面两个属性,相信大家对Grid布局有一个基本的认识了,想必对Grid二维布局的模式也有一些概念了,接下来才是Grid精彩之处,震撼灵魂的地方!

grid-template-areas

上面两个属性分别设置Grid行属性和列属性,grid-template-areas是设置Grid区域的。所谓区域是由一个或多个行、列、单元组成的一篇区域。首先看一下语法:

.container {
  grid-template-areas: 
    "<grid-area-name> | . | none | ..."
    "...";
}

其中
grid-area-name表示网格区域的名称
.表示空的网格区域
none 表示没有定义网格区域
在我们平时开发时,经常会出现上头下尾中间两栏布局的情况,下面我们使用grid-template-areas完成这样的布局。

<style>
.container{
  display:grid;
  grid-template-rows:60px auto 60px;
  grid-template-columns:100px 1fr;
  grid-template-areas:
    "header header"
    "left right"
    "footer footer";
}

.container .item:first-child{
  grid-area: header;
}

.container .item:nth-child(2){
  grid-area: left;
}

.container .item:nth-child(3){
  grid-area: right;
}

.container .item:nth-child(4){
  grid-area: footer;
}
</style>
.container{
  display:grid;
  grid-template-rows:60px 1fr 60px;
  grid-template-columns:100px 1fr;
  grid-template-areas:
    "header header"
    "left right"
    "footer footer";
}

.container .item:first-child{
  grid-area: header;
}

.container .item:nth-child(2){
  grid-area: left;
}

.container .item:nth-child(3){
  grid-area: right;
}

.container .item:nth-child(4){
  grid-area: footer;
}
.container{
  display:grid;
  grid-template-rows:60px 1fr 60px;
  grid-template-columns:100px 1fr;
  grid-template-areas:
    "header header"
    "left right"
    "footer footer";
}

.container .item:first-child{
  grid-area: header;
}

.container .item:nth-child(2){
  grid-area: left;
}

.container .item:nth-child(3){
  grid-area: right;
}

.container .item:nth-child(4){
  grid-area: footer;
}
</style>

<div class="container">
    <div class="item">1</div>
    <div class="item">2</div>
    <div class="item">3</div>
    <div class="item">4</div>
</div>

在Grid容器中,我们定义了6个网格单元,使用grid-tempalte-areas划分了header footer left right 四片区域;而在grid元素中,每个元素使用grid-area来指定元素所对应的grid区域。因此,我们虽然划分了6个单元,但可以使用四个元素来表示。

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

是不是很神奇呢,更神奇的是,grid-area-name是支持中文定义的。

以上grid-template的子属性就说完了,grid-template是以上那三个属性的简写方式,语法如下:

grid-tempalte:<'grid-template-rows'> / <'grid-template-columns'>`
`grid-tempalte:[ <line-names>? <string> <track-size>? <line-names>? ]+
               [ / <explicit-track-list> ]?

例如上面的例子可以这样简写

grid-template: 
    "header header" 60px
    "left right" 1fr
    "footer footer" 60px
    / 50px 1fr;

grid-gap

grid-gap用来描述Grid 区域之间间隙的尺寸大小。语法如下:

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

grid-gap是简写属性,也可以分别定义grid行间隔和grid 列间隔。

.container{
    grid-row-gap:10px;
    grid-column-gap:10px;
}

grid-gap与margin与padding不同,它不占用当前元素的盒模型的位置。

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)
上图显示的很清楚,3号元素的margin 与 padding 均为零。

place-items

place-items justify-itemsalign-items的简写方式
这两个属性分别定义了grid元素水平与垂直分布方式。
语法如下:

justify-items: stretch | start | end | center;
    align-items: stretch | start | end | center;

对于这四个属性,默认stretch,相信读者在熟悉了Flex布局后都不会陌生,这里不多做解释,直接看例子,以align-items 为例:

.container{
  display:grid;
  grid-template: 
    "header header" 160px
    "footer footer" 160px
    / 160px 160px;
  
  height:500px;
  grid-row-gap:10px;
  grid-column-gap: 10px;
}

首先定义四个grid单元,每个单元的长宽均为160px
接下来我们更改align-items的值

align-items:stretch
JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

align-items:center

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

align-items:start

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)

align-items: end

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)
为了方便大家理解,我用红框框出每个Grid单元所占用的空间。由此可以看出,place-items属性是用来表明一个元素在当前grid单元中的分布方式,这个元素的拉伸,居中等都是以grid单元作为参考的。

place-content

place-content同样是一个简写属性,它包括:justify-content 和 align-content,它表示grid元素在grid容器中的分布方式,只有当grid容器中有剩余空间的时候才起作用。
语法如下:

justify-content: stretch | start | end | center | space-between | space-around | space-evenly;
align-content: stretch | start | end | center | space-between | space-around | space-evenly;

属性值得含义同Flex;这里不再过多说明,读者可以自行验证。

grid-auto-rows 与 grid-auto-columns

grid-auto-rows 和 grid-auto-columns;用于当实际的Grid的元素多余划分的Grid元素时,定义多余Grid元素的长宽;
例如我们在HTML里面一定了五个Grid元素,但是在css中只定义了2*2的Grid单元,可以使用grid-auto来定义多出来的轨道的尺寸。

grid-auto-flow

grid-auto-flow的用法需要结合下面的元素属性来说明。

元素属性

grid-column-start grid-column-end

两个属性是用来定义Grid元素列方向上的起始与终止位置。
语法格式为:

grid-column-start: <number> | <name> | span <number> | span <name> | auto

其中:
number为起止第几条网格线
name 为网格线的名称
span <number>网格元素会跨越网格单元的数量
span <name> 当前的网格元素会在哪一个网格线上开始or终止

注意 使用span 如果是start,表示这个从开始的位置跨过的grid单元,如果是end 表示这个元素覆盖的grid单元。
grid-column是它们的简写方式,语法为:

grid-column:grid-column-start / grid-column-end

grid-row-start grid-row-end

grid-row属性与grid-column用法一致,这里不过多赘言,直接看例子:

.container{
  display:grid;
  grid-template-rows:[rone]1fr[rtwo]3fr[rthree]1fr[rfour];
  grid-template-columns: [cone]1fr[ctwo]5fr[cthree]2fr[cfour];
  height:500px;
}

.item:first-child{
  grid-column-start:1;
  grid-column-end:cfour;
  grid-row-start:rone;
  grid-row-end: 2;
  
}

.item:nth-child(2){
  
  grid-column-start:1;
  grid-column-end:span 1;
  grid-row-start:rtwo;
  grid-row-end: span cthree;
}

.item:nth-child(3){
  
  grid-column-start: ctwo;
  grid-column-end:4;
  grid-row-start:rtwo;
  grid-row-end: span cthree;
} 



.item:nth-child(4){
  grid-column-start:1;
  grid-column-end:4;
  grid-row-start:3;
  grid-row-end: span 4;
}

效果:

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)
首先在Grid容器中划分出9个grid单元,这九个单元被六个网格线所分割,并给这六个网格线命名。在四个Grid元素中定义横行的起始位置。
grid-row 与 grid-column结合使用,可以起到与Grid-template-areas同样的效果。

grid-area

grid-area我们在前面已经接触过一部分了,他与Grid容器中的grid-template-areas一起定义,也是grid-column与grid-row的简写属性,语法为:

grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;

name为grid区域的名称,与grid-tempalte-areas结合使用。

上面我们的例子就可以用grid-area表示:

.item:first-child{
    grid-area:1/rone/2/cfour;
}

place-self

justify-self

justify-self定义Grid元素的水平布局方式的,例如,我们在Grid容器中定义justify-items的属性为默认属性,而在某一个Grid元素中定义justify-self为center,那么其他元素表现为拉伸,这个元素则单独表现为居中。也就是说,justify-self在Grid容器中对应的属性是justify-items。
语法为:

.item {
    justify-self: stretch | start | end | center;
}

algin-self

align-self与justify-self一致,改变的是这个元素的垂直部署方式,与容器中align-items对应,
语法为:

.container {
    align-self: stretch | start | end | center;
}

由于这四个属性值已经在我们的系列文章中出现多次,这里不再多说。

plac-self是以上两个属性的简写方式,语法为:

.container {
    place-items: <align-self> / <justify-self>;
}

再论fr

Grid的所有属性已经介绍完毕了,在对Grid宇宙有了一个基本的认识后,我们再回头看一下Grid宇宙中出现的新尺寸单位——fr。
可能大家在刚刚结仇到fr这个单位时,都会认为它是%的一个别名;但是,我们来看最下面的例子:
我们在Grid容器中划分出四个Grid区域,并定义每个区域的宽度为25%,并定义每个Grid元素之间的gap宽70px;

.container{
  display:grid;
  grid-template-columns: repeat(4, 25%);
  grid-column-gap: 70px;
}

效果如下:
JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)
很明显,这里元素溢出了。这种情况是我们不想看到的。
下面,我们将25%替换为1fr看一下效果:

JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)
效果对比很明显。
而造成两者显著区别的原因是二者的计算空间方式的不同。使用百分比它的分母是父元素的width或者height,而fr的分母是父元素中剩余空间的尺寸;css会首先计算使用%和px定义的元素尺寸,剩下的空间再由fr元素进行比例分配。这就是使用fr不会出现元素溢出的情况。当然我们也可以使用calc避免溢出的尴尬,但是两种解决方案孰优孰略已经一目了然了。

Flex 与 Grid

Flex布局与Grid布局有很多相似的地方,例如justify-content和justify-items的用法。但是更多的是不同,最重要的是Grid开拓了二维布局的方式,相比于传统的css布局方式(Flex、bootstrp 12列),Grid开创了网格的概念,用户可以从横纵两个方面部署元素。正是因为如此,在Grid宇宙中,传统的css布局、尺寸属性就显得格格不入了。而Grid布局的二维特性所带来的整体观,使Grid在页面级别样式上更加游刃有余。而Flex相比于Grid 更加适合小组件上的样式开发,二者并不冲突相信在Grid 与 Flex双双加持之下,一定会收获更好的开发效果!
Grid布局还是一个较新潮的概念,我也是一般看资料学习,一边分享,由于缺少实际的开发经验,对于很多属性的应用场景还没有很深入的理解,故而有的属性一笔带过;如果我有理解不正确的地方,欢迎大家指正!

参考文献

MDN:Grid Layout
张鑫旭:写给自己看的display: grid布局教程
知乎:CSS 新的长度单位 fr 你知道么?

What's more:

JavaScript五十问——对比来说CSS的Grid与FlexBox(上篇)

相关推荐