CSS Grid布局了解


Hello ,CSS Grid

之前做一个分块展示页面,其页面布局大致如图所示:
image
遇到这样的布局,在不考虑浏览器兼容性的情况下,我认为使用Grid布局应该是一种很不错的选择 —— 容器元素间没有太深的层级关系,且容器外观大致相似。在此第一次尝试了Grid布局方式。

Grid 布局简介

Grid布局是目前(2020年5月文章撰写时,虽然一两年前就发布了)较新的一种布局方式,可以使得网页上的元素以较为简便的方式在页面横向和纵向上进行排布。

Grid布局是一种二维布局的方式。这一点和多年前盛行的表格布局相似,但比表格布局更加轻量、更加灵活、可访问性更强、布局更快。Flex以及 Inline-block / Float 等一维布局方式,在同一容器中排布元素时,仅能够在一个方向上排列 —— 要么行,要么列;如需转换排列方向,则需要在容器下再嵌套容器,在新的容器中排列。而如果使用 Grid 布局,容器元素嵌套层级更少。仅需一个外部的 Grid 容器与若干个位于内部的子元素,即可对子元素实现“扁平化”管理。

……好了,以上是笔者自己的介绍和看法,不一定权威。以下是一些参考链接:

CSS Grid 网格布局教程 – 阮一峰的网络日志

CSS Grid Layout – CSS: Cascading Style Sheets | MDN

想要以游戏的方式来了解CSS Grid布局?或许可以看看:Grid Garden – A game for learning CSS grid

简易Demo

你可以通过该Demo,快速了解CSS Grid布局的用途、使用方式,同时也为我们下方的其他讲解打个基础。

css/gird-layout-start.html

  1. HTML结构
<!--
重点关注:grid-cell元素上内联的grid-area属性。
-->
<div class="grid-wrap">
  <section class="grid-cell" style="grid-area:cell1">1</section>
  <section class="grid-cell" style="grid-area:cell2">2</section>
  <section class="grid-cell" style="grid-area:cell3">3</section>
  <section class="grid-cell" style="grid-area:cell4">4</section>
  <section class="grid-cell" style="grid-area:cell5">5</section>
</div>

外层grid-wrap是网格容器,其下直接包含了所有的子元素。

我们需要为每一个子元素指定一个grid-area属性,标识该子元素将会使用网格容器中的哪块区域。这里的属性值是一个自定义字符串。

  1. CSS规则定义
/* 网格容器 */ 
/* 重点关注
    display
    grid-template-areas
    grid-template-columns
    grid-template-rows
*/
.grid-wrap {
    height: 100%;
    display: grid;
    box-sizing: border-box;
    padding: 30px 0;
    grid-template-areas:
'cell1 cell3'
'cell1 cell4'
'cell2 cell5';
    grid-template-columns: 50% 50%;
    grid-template-rows: 25% 25% 50%;
}
/* 网格子元素 - 仅仅用于美化,无重要代码 */
.grid-cell {
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgb(0,91,151);
    background-image: linear-gradient(  -30deg,  rgb(46,49,146) 0%,  rgb(30,156,215) 39.4%,  rgb(0,113,188) 72.16%,  rgb(0,91,151) 100%  );
    margin: 15px 25px;
    border-radius: 40px;
    color: #fff;
    text-align: center;
    font-size: 5em
}

在此我们看一下网格容器(.grid-wrap)的样式。这里有几个之前没用过的属性:

  1. display: grid;
    将容器配置为grid容器
  2. grid-template-areas: 'cell1 cell3' 'cell1 cell4' 'cell2 cell5';
    配置容器布局,此处布局为三行二列布局。这样看会更好理解一些:
grid-template-areas: 
  'cell1 cell3'
  'cell1 cell4'
  'cell2 cell5'
;

在这里,我们将网格容器下的子元素按照这一布局进行放置,具体出现的位置由子元素各自设定的grid-area决定。

  1. grid-template-columns: 50% 50%;
    配置容器每列列宽。
  2. grid-template-rows: 25% 25% 50%;
    配置容器每行行高。

同时我们看一下网格子元素(.grid-cell)的样式,这里有个早前未使用过的属性:

  • grid-area:cell1;
    用于配置子元素在网格中的位置,此处简易Demo,属性值来自于父元素grid-template-areas定义的区域。该属性可以设置的值较为灵活,并非此处Demo这么简单,详见下方。

以上内容就是简易Demo的全部相关讲解,个人认为这一简易Demo大致上可以应付日常业务上所遇到的一些需求不高的布局,到此你对CSS Grid布局方式应该已经有了一个简单的印象。有关CSS Grid布局的更多细节,请继续阅读下文。

相关术语

文章写作过程中,笔者发现自己对一些内容的介绍用词并不是很准确,因此参照MDN的介绍整理了一下表格布局的相关术语,调整了措辞。

  1. 网格
    display值为grid的元素。在下文中被笔者称为”网格容器”。
  2. 网格线
    网格线是网格中每行、每列的分割线,从每行 / 每列的第一个元素起始的那根线算起,到最后一个元素终止的那根线结束。网格中第一根网格线索引记为1(注意不是0)。
  3. 网格轨道
    网格线与网格线之间的部分。
  4. 网格单元
    两根相邻行网格线与两根相邻列网格线之间交叉形成的区域(类比“井”字中间的部分)。
  5. 网格区域
    网格区域是由一个或多个网格单元组成的一块矩形区域 —— 非矩形区域是无效的。
  6. 间距
    相邻两个网格轨道之间的空隙,类比两个元素间不发生合并的margin。
  7. 网格轴
    网格的横轴、纵轴。
  8. 网格行
    网格中的水平轨道。
  9. 网格列
    网格中的垂直轨道。

通过网格容器设置子元素相关属性

这一章节,我们将为网格容器(display:grid)设置一些属性,来控制子元素的尺寸、间距等样式。

这里介绍的一些特性包括:

  1. 控制尺寸与位置
  • grid-template-areas属性
  • grid-template-columnsgrid-template-rows 属性
  • grid-template属性
  • repeat() 函数
  • minmax()函数
  • fr单位
  1. 控制间距
  • grid-row-gapgrid-column-gap属性
  • grid-gap / gap属性

确定子元素的位置、尺寸

设置子元素的位置

首先我们来看看Grid布局如何对子元素的位置进行确定。在这里我们使用的是grid-template-areas属性。

在使用grid-template-areas之前,首先我们需要为Grid的子元素设置grid-area属性,上方例子有5个子元素,我们分别将它们标识为cell1cell2cell3cell4cell5。这里相当于为这5个子元素命名,以供grid-template-areas使用。

接下来我们就可以在grid-template-areas中,使用这些元素的grid-area属性,来为这些元素布局 。

grid-template-areas属性定义了一个网格区域模板,描述了网格容器中各个网格区域的位置。子元素最终将根据这些网格区域进行定位。

我们来看一看上一节中的例子:

grid-template-areas: 
  'cell1 cell3'
  'cell1 cell4'
  'cell2 cell5'
;

此处美化了一下代码,可以看到我们为网格设置了一个三行二列布局。

image

浏览器会自动检查grid-template-areas属性是否存在可以合并的网格单元。若某几个网格单元同名、连续且可以构成矩形,即可合并为一个网格区域。这里的2个名为cell1的网格单元便满足了这个条件,因此被合并为了一个网格区域。

设置网格行、列尺寸

上一节中已经介绍了对网格行、列的尺寸进行设置的方式:

  • grid-template-columns – 每一列的宽
  • grid-template-rows – 每一行的高

这两个属性用于设置网格容器中各行、各列的宽与高。
默认情况下,倘若不对这两个属性进行设置,那么网格容器下各行、各列将会自动按照grid-template-areas里设置的布局规则,平均分配网格容器的宽、高。

基本语法类似:

grid-template-areas:
    'cell1 cell3'
    'cell1 cell4'
    'cell2 cell5';
grid-template-columns: 50% 50%;
grid-template-rows: 25% 25% 50%;

我们可以尝试把这两个属性统一为grid-template来进行书写,即:

grid-template: 25% 25% 50% / 50% 50%;
grid-template-areas:
    'cell1 cell3'
    'cell1 cell4'
    'cell2 cell5';

值得注意的是,grid-template属性实际上是grid-template-areasgrid-template-columnsgrid-template-rows三个属性的简写,此处方便理解,在后面单独补充了grid-template-areas属性。如果这里不进行补充,grid-template-areas将会被grid-template中对应的空值覆盖。

此处百分比是相对于网格容器尺寸的百分比。除了百分比外,这里的单位还可以是pxemvhvwpt等等等等。

为了书写便捷,这两个属性还可接收一些关键字和函数。下面我们来换一个行列稍微多一些的例子来看一看这些关键字与函数的作用。

  1. repeat() 函数
    用于简化重复布局的写法。该函数接收两个参数,第一个是重复次数,第二个是网格行或列的尺寸,该尺寸可接收若干个,根据行或列的数量依次重复往下设置。如图:image
grid-template-rows: repeat(3, 4em 10em);
grid-template-columns: repeat(2, 5em 30em);

相当于

grid-template-rows: 4em 10em 4em 10em 4em 10em;
grid-template-columns: 5em 30em 5em 30em;
  1. fr单位
    fr意为fraction,即“分数”,网格容器某一维度上的空间(或者说某一维度上固定空间分配完后剩余的空间)被平均分配后,这一维度上每一个轨道的宽度 / 高度相当于1fr。该单位并不是一个确切的单位,主要用于表示网格在两个维度上的伸缩因数,似乎和Flex中的flex-shrinkflex-grow以及flex-basis书写略有类似。
    此处,使用fr表示的尺寸可以与其它单位表示的尺寸一起使用。这些使用其它单位的尺寸被分配后,剩余的空间由使用fr单位的尺寸来进行分配。如图所示:
image
    grid-template-rows: 100px 1fr 2fr 3fr;
    grid-template-columns: 100px minmax(1px,1fr) 2fr;

Grid布局 – fr单位

上图将网格的首行、首列设置为了100px,其它行、列设为了以fr为单位的值。我们可以发现以fr为单位的网格,在两个维度上的比例关系满足我们为它们设置的值。

十分有趣的是,我们在这里将首行、首列在各自维度上的尺寸设为了固定值100px,而其它网格(以fr为单位的网格)尺寸则自动占满容器。此时若拖动改变窗口大小,可以发现首行首列在各自维度上尺寸是固定的,但是其他网格大小是自适应变化的 —— 这似乎是一个解决“如何实现左侧栏固定右侧栏自适应?”问题的新方案。

  1. minmax() 函数
    顾名思义,这一函数实际上就是用于将网格行、列的尺寸限制于某个范围之内,接收两个表示尺寸的参数。

经过笔者测试,第一个表示尺寸的参数(最小值),单位不能为frfr表示的是网格容器空间平均分配后一个网格轨道的宽度 / 高度,这个宽度 / 高度本身就是网格轨道的最大值,除非最终最小值大于这个值。

当容器某个维度尺寸过小、无法容纳网格轨道时,网格轨道在这一维度尺寸将会使用最小值,当容器很大时,若足以容纳网格轨道,则网格轨道将会使用最大值。

设置子元素间距

平常在开发时,若要调整元素间的间隔,我们能想到的方法莫过于为每个子元素增加margin值。类似:

image

不过缺点也显而易见。网格四周边缘的子元素也会应用这一margin值,导致网格容器内部四周也会存在边距。倘若我们不希望存在这些边距,且不能够改变HTML结构,该如何做呢? —— 莫非为在边缘的元素一个个设置margin-top/margin-right/margin-bottom/margin-left?😫

在 CSS Grid布局中,有一个专门调整网格轨道之间间隔的属性,那就是grid-gap(在Chrome 84下测试时发现该属性可再简写为gap)。grid-gap接受一个长度值,用于定义行列间距;同时也可以接受两个空格分隔的长度值,分别用于定义列间距和行间距 :
image

grid-gap: 0.5cm;

grid-gap实际上是grid-row-gapgrid-column-gap两个属性的缩写,在这里相当于:

grid-row-gap: 0.5cm;
grid-column-gap: 0.5cm;

在下图中,我们将行与行之间的间距设为了10px,列与列之间的间距设为了200px

image
grid-row-gap: 10px;
grid-column-gap: 200px;

通过Grid子元素设置子元素自身相关属性

这一章节,我们将为Grid容器下的子元素设置一些属性,来控制子元素自身的样式。

这里介绍的一些可在子元素上设置的属性包括:

  1. grid-row-startgrid-column-start 、 grid-row-endgrid-column-end属性
  2. grid-columngrid-row 属性
  3. grid-area 属性

先来说一下他们的关系:

  1. grid-row-start / grid-column-start / grid-row-end / grid-column-end四个基本属性,对应控制子元素的:起始行网格线 / 起始列网格线 / 终止行网格线 / 终止列网格线grid-columngrid-row以及grid-area属性则对这些基本属性进行了简写
  2. grid-columngrid-column-start / grid-column-end的简写,grid-rowgrid-row-start / grid-row-end 的简写
  3. grid-areagrid-row-start / grid-column-start / grid-row-end / grid-column-end的简写

设置子元素区域

我们可以使用下面这些属性组合来为当前子元素设置占据的网格区域:

  1. grid-row-start + grid-column-start+ grid-row-end + grid-column-end
  2. grid-row + grid-column
  3. grid-area

其实这都是同一种方式,仅仅是缩写或是全写组合的区别。我们先从最简单的第一种方式看起。

grid-row-start + grid-column-start+ grid-row-end + grid-column-end

如上文所言 —— 子元素有关Grid布局的其它属性(grid-rowgrid-columngrid-area)的值由这四个基本属性组合而成。

这四个基本属性可以接受这些值:

  1. 整数,表示网格线的索引,起始值为1。若给定的整数是一个负数,那么网格线的索引将会从最后一个算起(即倒数),此时起始值为-1。子元素将会从给定的网格线索引处开始 / 结束。形如:
    grid-row-start: 1;
    grid-column-start: 2;
    grid-row-end: 3;
    grid-column-end: 4;

表示该子元素的行从网格中第1根网格线开始,到第3根网格线结束;列则从第2根网格线开始,到第4根网格线结束。
image

  1. 父元素grid-template-areas属性中设定的网格区域名称。子元素起始 / 终止网格线默认根据给定网格区域来确定,根据属性名是start还是end来确定是使用区域的起始网格线还是终止网格线(一般来说会覆盖给定的网格区域);可选在网格区域名称后方加-start-end后缀强制规定是使用该区域的起始网格线还是终止网格线。形如:
    grid-row-start: cell1;
    grid-column-start: cell4;
    grid-row-end: cell8-start;
    grid-column-end: cell8-end;

表示该子元素的行从cell1区域的起始网格线开始,到cell8区域的起始网格线结束;列则从cell4区域的起始网格线开始,到cell8的终止网格线结束。

表示该子元素从cell1区域的行起始网格线、cell4区域列起始网格线开始,到cell8区域行起始网格线、cell8区域列终止网格线结束。(糟糕的措辞,建议看图理解。。。😵)
image

  1. span关键字 + 正整数,表示从某个网格线开始算起,再延伸多少网格线(类似于表格中的合并单元格)。必须在同一维度的属性上成对使用形如这样的值,且该对属性中另一个值必须为表示网格线索引的整数或区域名称,使子元素占据的区域有具体的起始 / 终止网格线。形如:
    grid-row-start: cell1;
    grid-column-start: span 1;
    grid-row-end: span 3;
    grid-column-end: cell8-end;

表示该子元素的行从cell1区域的起始网格线开始,延伸3个网格线结束;列则从cell8的终止网格线开始,延伸1个网格线结束。
image

注意:此处span后的整数必须是正整数,表示延伸了几根网格线。例如此处设为span -3,表示延伸 -3 个网格线 —— 这显然不符合常理。

grid-row + grid-column

grid-row表示子元素起始行网格线(grid-row-start)、终止行网格线(grid-row-end),grid-column表示子元素起始列网格线(grid-column-start)、终止列网格线(grid-column-end)。

它们和上文所述的四个基本属性的语法一致,但可接受两个值,两个值之间使用 / 分隔。前一个值表示起始网格线,后一个值表示终止网格线。形如:

grid-row: cell1 / 3;
grid-column: cell5 / span 2
image

当然该属性也可以只接受一个值:

  • 如果该值是网格区域名称,则子元素的起始、终止网格线即对应该区域的起始、终止网格线。相当于如下设置:
// 缩写
grid-row: cell1;
// 全写
grid-row-start: cell1;
grid-row-end: cell1;
  • 如果该值是网格线索引,则子元素起始网格线为该值,终止网格线为auto。相当于如下设置:
// 缩写
grid-row: 1;
// 全写
grid-row-start: 1;
grid-row-end: auto;

grid-area

表示的是子元素在网格中所在区域。

该属性接受的值形式十分多样😵

  • 如果设置了四个值 ——
    • 即上文提及的的四个基本属性。四个基本属性在该属性中以/分隔进行设置,顺序是:grid-row-start / grid-column-start/ grid-row-end / grid-column-end
  • 如果设置了三个值 ——
    • 若第二个值为数值,则第四个值自动设为auto
    • 若第二个值为区域名称,则第四个值自动设为该网格名称。
  • 如果设置了两个值 ——
    • 若某个值为数值,则不足部分对应值设为auto
    • 若某个值为区域名称,则不足部分对应值设为该区域名称;
  • 如果设置了一个值 ——
    • 若这个值为数值,则不足部分对应值设为auto
    • 若这个值为区域名称 —— 则子元素所在位置便是区域名称所指定的位置,相当于将四个值全设为该区域名称。参考下方:grid-area:标识子元素?

grid-area:标识子元素?

回到整篇文章中最开始介绍的简易Demo。在此前笔者认为 grid-area 的作用是用于标识子元素的。
经过上文的了解,笔者发现之前所理解的所谓“标识子元素”的说法是不准确的,此处仅仅是因为子元素grid-area设置的网格区域只有一个值,且恰好和grid-template-areas定义的网格一致,这就误打误撞刚好实现了我们想要的效果。当然对于初学者来说,如此理解也没有问题。👀

上文我们根据子元素的 grid-area 属性,在网格容器上使用grid-template-areas对子元素进行排布。我们为每个子元素的 grid-area 设置了不同的值,来标识子元素,从而在grid-template-areas属性中以直观的方式进行了布局。

<style>
    .grid-wrap {
        height: 100%;
        display: grid;
        grid-template: 25% 25% 50% / 50% 50%;
        grid-template-areas:
            'cell1 cell3'
            'cell1 cell4'
            'cell2 cell5';
    }
    .grid-cell {
        background-color: #333;
        color:#fff
    }
</style>
<div class="grid-wrap">
    <section class="grid-cell" style="grid-area:cell1">1</section>
    <section class="grid-cell" style="grid-area:cell2">2</section>
    <section class="grid-cell" style="grid-area:cell3">3</section>
    <section class="grid-cell" style="grid-area:cell4">4</section>
    <section class="grid-cell" style="grid-area:cell5">5</section>
</div>

以第一个.grid-cell元素为例:

/* 这里我们让该子元素出现在cell1的位置 */
grid-area: cell1;
/* 相当于 */
grid-area: cell1 / cell1 / cell1 / cell1;
/* 相当于 */
grid-row-start: cell1;
grid-column-start: cell1;
grid-row-end: cell1;
grid-column-end: cell1;

评论