写给自己看的display: grid布局教程

这篇文章发布于 2018年11月6日,星期二,00:59,归类于 CSS相关。 阅读 5418 次, 今日 80 次 13 条评论

 

一、前言&索引

<div>这类块状元素元素设置display:grid或者给<span>这类内联元素设置display:inline-grid,Grid布局即创建!例如:

div {
    display: grid;
}

此时该div就是“grid容器”,其子元素称为“grid子项”。

//zxx: grid和inline-grid区别在于,inline-grid容器为inline特性,因此可以和图片文字一行显示;grid容器保持块状特性,宽度默认100%,不和内联元素一行显示。

在Grid布局中,所有相关CSS属性正好分为两拨,一拨作用在grid容器上,还有一拨作用在grid子项上。具体参见下表,点击可快速索引。

作用在grid容器上 作用在grid子项上

Grid布局相关CSS属性虽然很多,但是实际上都不难理解,难的是这些属性不太容易记住,需要多多实战手写才能信手拈来。

Grid布局是一个二维的布局方法,纵横两个方向总是同时存在。其中的很多布局概念跟中国农田的布局是完全匹配的。

田地

因此,在我看来,Grid布局就像是“分田种地”。故事是这样的,张老板是个程序员,省吃俭用攒了点小钱,然后老家因为城镇化建设,农村都没什么人,土地都荒废在那里,于是就承包了一块地,打算养养鱼,种种果树。承包的地方很挺大,如何划分土地就成了问题,于是张老板打算借助Grid布局来划分。

二、作用在grid容器上的CSS属性

1. grid-template-columns和grid-template-rows

这两个CSS属性用来对田地进行基本的划分,columns是列的意思,表示竖直方向的划分;rows是行的意思,表示水平方向的划分。现实世界中,农田的布局构造一般下面两种:

  1. 田地A-田地B,下面是土地C-土地D,就是“田”这个字的构造,只不过4块地之间分隔就是个不能走路的小沟沟,宽度可以忽略不计。

    分隔不明显的田地

  2. 田地A-田垄-田地B,下面是土地C-田垄-土地D,也是“田”这个字的构造,只不过4块地之间分隔是个可以走路的田垄,有的地方也叫土埂。

    分隔明显的田地

这里的划分语法就和上面的农田划分一致,如下:

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

用中文表示就是:

.container {
  grid-template-columns: <田地> ... 或 <田垄> <田地> ...;
  grid-template-rows: <田地> ... 或 <田垄> <田地> ...;
}

也就是:

  • <track-size>:划分田地的尺寸。可以是长度值,百分比值,以及fr单位(网格剩余空间比例单位)。
  • <line-name>:中间用来走路的田垄的名字,可以任意命名。

看一个简单例子:

.container {
    grid-template-columns: 80px auto 100px;
    grid-template-rows: 25% 100px auto 60px;
}

grid-template-columns后面3个值,表示分为了3列,从左往右每列的尺寸分别是80px,auto(自动)和100px
grid-template-rows属性值含4个值,表示分为了4行,从上往下每行的高度分别是25%100px,auto(自动)和60px

基本尺寸划分示意图

实时效果如下所示:

宽80px
高25%

宽auto
高25%

宽100px
高25%

宽80px
高100px

宽auto
高100px

宽100px
高100px

宽80px
高auto

宽auto
高auto

宽100px
高auto

宽80px
高60px

宽auto
高60px

宽100px
高60px

我们还可以给“田垄”,也就是网格分隔线进行命名,语法是使用[]包裹我们自定义的命名,可以是中文,例如:

.container {
    grid-template-columns: [第一根纵线] 80px [纵线2] auto [纵线3] 100px [最后的结束线];
    grid-template-rows: [第一行开始] 25% [第一行结束] 100px [行3] auto [行4] 60px [行末];
}

实时效果如下,选中对应网格线的名称可以高亮其位置:

宽80px
高25%

宽auto
高25%

宽100px
高25%

宽80px
高100px

宽auto
高100px

宽100px
高100px

宽80px
高auto

宽auto
高auto

宽100px
高auto

宽80px
高60px

宽auto
高60px

宽100px
高60px

为何要给网格线命名呢?

Grid布局的好比街道划分,田地划分,这中间分隔的线通常就是道路或者田垄,如果我们不给这些道路起个名字,回头想要描述某片区域的时候就不好描述。比方说:

南京东路东起外滩即中山东一路,西至西藏中路。

因为我们给道路命了名称,因此,我们在描述某个区域的时候,就好描述,别人也好辨认。但如果没有命名,而是下面这样描述:

南京东路东起靠近黄浦江第1条路,靠近黄浦江第8条路。

得,这个区域描述就有问题,万一哪天封路,或者新建了条路,岂不就混乱了?

也就是说,给Grid布局中的分隔线命名,为的就是可以更好地对区域进行描述。如果我们没有描述某片区域的的需求,自然也不需要命名了。

双命名

由于网格中中间区域的网格线是两边格子公用的,就像道路有两边,因此,我们起名字的时候可以起两个名称(使用空格分隔),分别表示两侧。例如:

.container {
    grid-template-columns: [第一根纵线] 80px [第1根纵线结束 第2根纵线开始] 100px [最后的结束线];
}

repeat语法

有时候,我们网格的划分是很规律的,例如,基于40px创建栅格,要是我们布局宽度960px,岂不是要写24次40px,实在套啰嗦了,此时,就可以使用repeat()语法,如下示意:

.container {
    grid-template-columns: repeat(24, 40px [col-start]);
}

等同于:

.container {
    grid-template-columns: 40px [col-start], 40px [col-start], /* ...省略20个...*/, 40px [col-start], 40px [col-start];
}

fr单位是什么?

fr是单词fraction的缩写,表示分数。

  • 先从简单例子看起:

    .container {
        grid-template-columns: 1fr 1fr 1fr;
    }

    1:1:1,网格宽度三等分,实时效果如下:

    宽1fr
    占据1/3

    宽1fr
    占据1/3

    宽1fr
    占据1/3

  • 如果有固定尺寸值,则划分剩余空间大小,例如:

    .container {
        grid-template-columns: 200px 1fr 1fr 1fr;
    }

    4列,后面3列宽度是grid容器宽度减去200像素后的1/3大小,实时效果如下:

    宽200px

    宽1fr

    宽1fr

    宽1fr

  • 如果和auto混用会如何呢?

    .container {
        grid-template-columns: auto 1fr 1fr 1fr;
    }

    宽auto

    宽1fr

    宽1fr

    宽1fr

    从上面效果可以看出,当有设置fr尺寸的时候,auto的尺寸表现为“包裹”,为内容宽度。如果没有设置fr尺寸的网格,则表现为拉伸。

  • 如果fr数值之和小于1又当如何?

    .container {
        grid-template-columns: auto 0.25fr .25fr .25fr;
    }

    宽auto

    宽0.25fr

    宽0.25fr

    宽0.25fr

  • 这里计算就相对复杂些,首先,由于第一个网格尺寸设置为auto,因此fr计算需要的剩余空间尺寸是grid容器的宽度减去“宽auto”这几个字符的宽度。所以,后面3个0.25fr元素的宽度是:(容器宽度 - “宽auto”字符宽度) * 0.25。然后剩余尺寸就是第一个网格宽度。

2. grid-template-areas

area是区域的意思,grid-template-areas就是给我们的网格划分区域的,就好像张老板承包的土地划分不同区域养殖不同的农作物或者水产品。

语法如下:

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

其中:

grid-area-name
对应网格区域的名称。
.
表示空的网格单元格。
none
没有定义网格区域。

我们还是通过案例了解这个CSS属性。张老板承包了一块地,然后划分成了3*4共12个小格子,然后张老板希望最上面3个格子种葡萄,最下面3个格子种西瓜,中间6个格子,左边2个养龙虾,右边4个养鱼。如下图示意:

养殖承包区域划分示意

则对应CSS代码如下:

.container {
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: 1fr 1fr 1fr 1fr;
    grid-template-areas: 
        "葡萄 葡萄 葡萄"
        "龙虾 养鱼 养鱼"
        "龙虾 养鱼 养鱼"
        "西瓜 西瓜 西瓜";
}

12个格子,四片区域,因此,我们grid子项只需要4个元素即可,HTML示意如下:

<div class="container">
    <div class="putao"></div>
    <div class="longxia"></div>
    <div class="yangyu"></div>
    <div class="xigua"></div>
</div>

此时grid子项只要使用grid-area属性指定其隶属于那个区域就可以了(支持中文区域名称):

.putao { grid-area: 葡萄; }
.longxia { grid-area: 龙虾; }
.yangyu { grid-area: 养鱼; }
.xigua { grid-area: 西瓜; }

实时Grid布局效果如下:

葡萄种植区

龙虾养殖区

鱼类养殖区

西瓜种植区

注意:如果我们给网格区域命了名,但是没有给网格线命名,则会自动根据网格区域名称生成网格线名称,规则是区域名称后面加-start-end。例如,某网格区域名称是“葡萄”,则左侧column线名称就是“葡萄-start”,左侧column线名称就是“葡萄-end”。

以及,我们的网格区域一定要形成规整的矩形区域,什么L形,凹的或凸的形状都是不支持的,会认为是无效的属性值。

//zxx: 实际开发的时候,葡萄种植区就是头部区域,龙虾就是侧边栏区域,鱼类养殖区就是主区域,西瓜种植区就是底部区域。

3. grid-template

grid-templategrid-template-rowsgrid-template-columnsgrid-template-areas属性的缩写。

语法如下:

.container {
    grid-template: none;
}
.container {
    grid-template: <grid-template-rows> / <grid-template-columns>;
}

其中none表示将3个CSS属性都设置为初始值。

举个例子,前面张老板养殖区划分,用grid-template缩写表示就是:

.container {
    grid-template: 
        "葡萄 葡萄 葡萄" 1fr 
        "龙虾 养鱼 养鱼" 1fr 
        "龙虾 养鱼 养鱼" 1fr 
        "西瓜 西瓜 西瓜" 1fr
        /1fr 1fr 1fr;
}

实时效果如下:

葡萄种植区

龙虾养殖区

鱼类养殖区

西瓜种植区

由于grid-template不会重置一些隐式的grid属性(如grid-auto-columnsgrid-auto-rowsgrid-auto-flow),因此,大多数时候,还是推荐使用grid代替grid-template

4. grid-column-gap和grid-row-gap

grid-column-gapgrid-row-gap属性用来定义网格中网格间隙的尺寸。你可以理解成田地之间走路的田垄宽度。

语法如下:

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

其中:

<line-size>
网格间的间隙尺寸。

实例说话,给定一个简单的2×2网格,设置水平网格间隙10px,垂直方向15px,如下:

.container {
    grid-template-columns: 2fr 1fr;
    grid-template-rows: 1fr 2fr;
    grid-column-gap: 10px;
    grid-row-gap: 15px;
}

浏览器实时布局渲染如下:

5. grid-gap

CSS grid-gap属性是grid-column-gapgrid-row-gap属性的缩写。语法如下:

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

先横row后竖column,这个比较好记忆,古语有云:“横竖都是死”,先横后竖,网格的间隙就像是汉字“田”中间的那个“十”,按照汉字书写,先横后竖,就记住了。

例如,上面的2×2网格间隙案例也可以写作:

.container {
    grid-template-columns: 2fr 1fr;
    grid-template-rows: 1fr 2fr;
    grid-gap: 15px 10px;
}

效果一样的,这里就不占据篇幅重复示意了。

6. justify-items

justify-items指定了网格元素的水平呈现方式,是水平拉伸显示,还是左中右对齐,语法如下:

.container {
    justify-items: stretch | start | end | center;
}

其中:

stretch
默认值,拉伸。表现为水平填充。
start
表现为网格水平尺寸收缩为内容大小,同时沿着网格线左侧对齐显示(假设文档流方向没有变)。
end
表现为网格水平尺寸收缩为内容大小,同时沿着网格线右侧对齐显示(假设文档流方向没有变)。
center
表现为网格水平尺寸收缩为内容大小,同时在当前网格区域内部水平居中对齐显示(假设文档流方向没有变)。

各个属性值实时效果如下(点击单选框体验不同属性值布局效果):

示意图片1
示意图片2
示意图片3
示意图片4

7. align-items

align-items指定了网格元素的垂直呈现方式,是垂直拉伸显示,还是上中下对齐,语法如下:

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

其中(假设文档流方向为网页默认):

stretch
默认值,拉伸。表现为垂直填充。
start
表现为网格垂直尺寸收缩为内容大小,同时沿着上网格线对齐显示。
end
表现为网格垂直尺寸收缩为内容大小,同时沿着下网格线对齐显示。
center
表现为网格垂直尺寸收缩为内容大小,同时在当前网格区域内部垂直居中对齐显示。

各个属性值实时效果如下(点击单选框体验不同属性值布局效果):

示意图片1
示意图片2
示意图片3
示意图片4

8. place-items

place-items可以让align-itemsjustify-items属性写在单个声明中。语法如下:

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

这里顺序是align-items在前,justify-items在后。首字母a,j,a,j,a,j,口中不断重复,有没有发现跟angelababy发音很像,没错,记住angelababy我们也就记住这里的顺序了。又或者有句古话,叫做“合纵连横”,这种网格对齐,就有“合纵连横”的意味在里面,纵在前,横在后,也可以方便我们记忆。

据说Edge15之前版本不支持place-items属性(自己未实测),因此,如果有兼容性顾虑,建议还是分开书写。

9. justify-content

justify-content指定了网格元素的水平分布方式。此属性仅在网格总宽度小于grid容器宽度时候有效果。例如,我们网格设定的都是固定的宽度值,结果还有剩余空间。例如:

.container {
    display: grid;
    width: 300px;
    grid-template: 100px 100px/100px 100px;
}

此时,水平和垂直方向都有100px的剩余,justify-content属性此时就有用武之地了。

语法如下:

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

其中:

stretch
默认值。拉伸,宽度填满grid容器,拉伸效果需要网格目标尺寸设为auto时候才有效,如果定死了宽度,则无法拉伸。
start
默认值。逻辑CSS属性值,与文档流方向相关。默认表现为左对齐。
end
逻辑CSS属性值,与文档流方向相关。默认表现为右对齐。
center
表现为居中对齐。
space-between
表现为两端对齐。between是中间的意思,意思是多余的空白间距只在元素中间区域分配。使用抽象图形示意如下:

space-between分布效果示意

space-around
around是环绕的意思,意思是每个flex子项两侧都环绕互不干扰的等宽的空白间距,最终视觉上边缘两侧的空白只有中间空白宽度一半。使用抽象图形示意如下:

space-around分布效果示意

space-evenly
evenly是匀称、平等的意思。也就是视觉上,每个flex子项两侧空白间距完全相等。使用抽象图形示意如下:

space-evenly分布效果示意

眼见为实,点击下面对应单复选框,可以看到实时的布局效果:

示意图片1
示意图片2
示意图片3
示意图片4

上面案例和下面案例中的grid布局相关CSS都是:

.container {
    grid-template: auto auto/auto auto;
}

10. align-content

align-content可以看成和justify-content是相似且对立的属性,justify-content指明水平方向grid子项的分布方式,而align-content则是指明垂直方向每一行grid元素的分布方式。如果所有grid子项只有一行,则align-content属性是没有任何效果的。

语法如下:

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

其中:

stretch
默认值。每一行flex子元素都等比例拉伸。例如,如果共两行flex子元素,则每一行拉伸高度是50%。
start
逻辑CSS属性值,与文档流方向相关。默认表现为顶部堆砌。
end
逻辑CSS属性值,与文档流方向相关。默认表现为底部堆放。
center
表现为整体垂直居中对齐。
space-between
表现为上下两行两端对齐。剩下每一行元素等分剩余空间。
space-around
每一行元素上下都享有独立不重叠的空白空间。
space-evenly
每一行元素都完全上下等分。

眼见为实,我们给flex容器设置高度500像素,然后点击下面对应单选框,可以看到实时的布局效果:

示意图片1
示意图片2
示意图片3
示意图片4

11. place-content

place-content可以让align-contentjustify-content属性写在一个CSS声明中,也就是俗称的缩写。语法如下:

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

这里顺序是align-content在前,justify-content在后。首字母a,j,a,j,读个几遍,是不是和angelababy发音一致,记住angelababy就记住这里的顺序了。又或者有句古话,叫做“合纵连横”,这种网格分布,就有“合纵连横”的意味在里面,纵在前,横在后,也可以方便我们记忆。

据说Edge15及其之前版本尚不支持place-content属性(自己未实测),因此,如果有兼容性顾虑,建议还是分开书写。

12. grid-auto-columns和grid-auto-rows

指定任何自动生成的网格轨道(也称为隐式网格轨道)的大小。 当网格项目多于网格中的单元格或网格项目放置在显式网格之外时,将创建隐式轨道。

用张老板承包土地的案例解释就是:

  1. 土地划分,计划分成16块区域搞农业,材料都买好了,结果发现承包的土地只能放下12块区域,多的4块怎么办呢?就在承包土地外面种点东西,不要浪费。
  2. 土地划分,计划上面种葡萄,底部种西瓜。但是,种植的时候搞错了,西瓜种到了承包区域之外。

上面这两种情况都是因为各种原因在自己土地之外也种了东西。如果张老板想要对不在自己土地上的种植区域也进行尺寸规划,该怎么办?此时就需要用到grid-auto-columnsgrid-auto-rows属性,就是应付这种场景的。

//zxx: 在Grid布局中,这些非正常网格称为“隐式网格”,在规定容器内显示的称之为“显式网格”。

语法如下:

.container {
    grid-auto-columns: <track-size> ...;
    grid-auto-rows: <track-size> ...;
}

其中:

<track-size>
划分田地的尺寸。可以是长度值,百分比值,以及fr单位(网格剩余空间比例单位)。

我们通过一个实例来感受下grid-auto-columnsgrid-auto-rows属性的样式表现。CSS如下:

.container {
    display: grid;
    width: 150px;
    grid-template-columns: 60px 60px;
    grid-template-rows: 30px 90px;
    grid-auto-columns: 60px;
}
.item-a { 
    grid-column: 1 / 2;
    grid-row: 2 / 3;
}
.item-b { 
    /* 容器水平只有2个格子,但这里设定的是第3个,隐式网格创建 */
    grid-column: 3 / 4;
    grid-row: 2 / 3; 
    background-color: rgba(255,255,0, .5);
}

实时效果如下,.item-b宽度强制表现为了60px,否则,则表现为auto,在这里,则是可怜巴巴填满剩余的30px

.item-a

.item-b

13. grid-auto-flow

grid-auto-flow属性控制没有明确指定位置的grid子项的放置方式。比方说定义了一个5*2的10格子,共有5个元素,其中2个元素指定了放在哪个格子里,还有3个则自生自灭排列。此时,这3个元素如何排列就是由grid-auto-flow属性控制的。

语法如下:

.container {
  grid-auto-flow: row | column | row dense | column dense
}

其中:

row
默认值。没有指定位置的网格依次水平排列优先。
column
没有指定位置的网格依次垂直排列优先。
dense
dense这个英文是稠密的意思。如果有设置,则表示自动排列启用“密集”打包算法。如果稍后出现的网格比较小,则尝试看看前面有没有合适的地方放置,使网格尽可能稠密紧凑。此属性值仅仅改变视觉顺序,会导致DOM属性和实际呈现顺序不符合,这对于可访问性是不友好的,建议谨慎使用。

实例说话,已知CSS如下:

.container{grid-template: 1fr 1fr 1fr/1fr 2fr 2fr 1fr 2fr;}
.item-a { grid-column: 1; grid-row: 2 / 4; }
.item-b { grid-row: 1 / 3; }
.item-c {}
.item-d {}
.item-e {}

也就是.item-a.item-b水平位置固定,点击下面单选项,体验布局变化。

.item-a

.item-b

.item-c

.item-d

.item-e

  • 选中row,水平排列,此时.item-c高度足够放在左上角那个网格中,因此,视觉顺序是c, b, d, e。
  • 选中column,垂直排列,此时.item-c宽度不足够放在左上角那个网格中,因此,视觉顺序(先上下后左右)是b, c, d, e。
  • 选中row dense,水平排列,同时前面有空就钻。视觉顺序同row属性。
  • 选中column dense,垂直排列,此时.item-c放置在左上角那个网格中,因此,视觉顺序(先上下后左右)是c, b, d, e,b和d垂直排列。

14. grid

是下面所有这些CSS属性的缩写集合,grid-template-rowsgrid-template-columnsgrid-template-areasgrid-auto-rowsgrid-auto-columnsgrid-auto-flow

语法如下:

  • grid: none

    none表示设置所有的子属性为初始值。

  • grid: <grid-template>

    grid-template用法一致。例如这样:

    .container {
        grid: 100px 300px / 3fr 1fr;
    }

    等同于下面:

    .container {
        grid-template-rows: 100px 300px;
        grid-template-columns: 3fr 1fr;
    }
  • grid: <grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>? 

    问号?表示0或1,可有可无的意思。也就是dense关键字和grid-auto-columns值都可以省略。

    具体说明:

    • auto-flow && dense?其实就是grid-auto-flow属性的值,等同于rowcolumnrow densecolumn dense

      但这里rowcolumn这两个关键字却使用了auto-flow这一个关键字代替了。那岂不有问题:什么时候解析成row,什么时候解析成column呢?

      原来,是根据auto-flow关键字是在斜杠的左侧还是右侧决定的。如果auto-flow关键字在斜杠左侧,则解析为row,如果是在右侧,则解析为column。这里的语法是在斜杠的右侧,因此,会将grid-auto-flow解析为column

    • <grid-auto-columns>后面有个问号?,因此是可以省略的,如果省略,则将grid-auto-columns解析为auto

    我们通过几个案例学习这里的语法:

    .container {
        grid: 100px 300px / auto-flow 200px;
    }

    上面CSS代码省略了dense关键字,启用了<grid-auto-columns>,因此,等同于下面CSS:

    .container {
        grid-template-rows: 100px 300px;
        grid-auto-flow: column