现代 CSS

图解CSS: Grid布局(Part2)

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

在上一节中,主要介绍了 CSS 网格布局中的一些重要概念和相关术语。从这些概念和术语中,从侧面也再次说明CSS 网格布局到目前为止是最为复杂的一个布局系统,换句话说,CSS 网格布局系统中会有很多属性,这些属性和 Flexbox 布局有些类似,有些属性只能作用于网格容器,有些属性只能作用于网格项目。而在这篇文章中,先从可用于网格容器的属性,即使用display属性创建网格容器和网格项目, grid-template-areasgrid-template-columnsgrid-template-rows 以及这三个属性的简写属性grid-template 来定义显式网格。如果感兴趣的请继续往下阅读。

网格布局中的属性

CSS 网格布局中涉及到的属性和 Flexbox 布局类似,可分为两个部分,其中一部分可用于 网格容器 的属性;另一部分是可用于 网格项目 的属性。接下来我们来看看这些属性的使用和所起的作用。

可用于网格容器的属性

到目前为止,CSS 网格布局模块 Level 1Level 2Level 3 几个不同版本规范的定义的可以用于网格容器的属性主要有:

在这些属性列表中,有些属性到现在还未得到浏览器的支持,或者仅得到个别主流浏览器的支持,比如前面提到的 subgrid 属性,而且有些还仅是实验性的属性,比如 Level 3 中的 masonry-auto-flow。这里要特别提出的是 CSS 网格布局模块 Level 3 ,目前还是处于规范的 ED 阶段,里面所提到的部分属性还处于不确定状态,有可能会随着后面的规范完善程度随之改变,因此,我们在这里不会在 Level 3 规范中提到属性花多少时间。

创建网格容器和网格项目 display

创建网格容器和网格项目很简单,只需要在容器上显式设置 display 的值为 gridinline-grid,该容器就成为网格容器,其子元素以及文本节点,伪元素就成为网格项目。

  • griddisplay 取值为 grid 时会使一个元素产生一个网格容器,当放在流式布局中时,它是块状的,称之为块网格容器
  • inline-griddisplay 取值为 inline-grid 时会使一个元素产生一个网格容器,当放在流式布局中时,它是内联的,称之为内联的网格容器

一个网格容器为其内容建立了一个独立的网格格式化上下文(Grid Formatting Content,即 GFC)。这与建立一个独立的块格式化上下文(Block Formatting Content,即 BFC)是一样的,只是使用了网格布局而不是块布局。网格容器的内容被布置成一个网格,网格线形成了每个网格项目所含块的边界。

网格容器不是块状容器,因此一些块状布局而设计的属性在网格布局中并不适用。特别是:

  • floatclear 对网格项目没有影响。然而, float 属性仍然会影响到网格容器的子项上的 display 的计算值,因为这发生在网格项被确定之前
  • vertical-align 对一个网格项没有影响
  • ::first-line::first-letter 伪元素不适用于网格容器,而且网格容器不会为其祖先提供第个格式化的行或第一个字母

如果一个元素指定 display 的值是 inline-grid,并且该元素是浮动的或绝对定位的,那么该元素的 display 的计算值是 grid

虽然在一个元素上显式设置 display 的值为 gridinline-grid 值,但你看到的效果和 display 值为 blockinline 相似的效果,比如下面这个示例:

<!-- HTML -->
<div class="grid__container">
    <div class="grid__item">Grid Item</div>
    Anonymous Item
    <div class="grid__item" style="float: left">Float Element</div>
</div>
<span>Inline Element</span>

/* CSS */
.grid__container {
    --display: grid;
    display: var(--display);
}

这是因为没有在网格容器上显式创建任何行或列,但事实上此时创建的是一个真实的网格容器,只不过这个时候网格是一个单列网格,网格的行由网格容器的子元素所决定,并且它们在单列中一个接一个地显示。从视觉上看,它们就像块状元素。

你可能已经看到了,示例中的“Anonymous Item” 是一个文本节点,并没有包裹在一个元素中,但它属于网格容器的子元素,它也成为一个网格项。网格容器的伪元素,比如示例中的 ::before::after 也将成为网格项目。

示例中 grid 切换到 inline-grid 时,网格容器变成一个内联级的盒子(内联网格容器),然而,其直接的子元素仍然是网格项目,其表现行为与块容器中的网格项目相同。

另外,在示例中的容网格容器 div.grid__container 下面有一个 span元素放置了一串文字,当 display 的值为 grid 时,该网格容器是个块级网格容器,其表现形为类似于块级元素,所以 span 元素会在另一行开始排列;当 grid 切换到 inline-grid 时,该网格容器就变成了一个内联级网格容器,所以 span 元素可以在它的旁边显式。

注意,你可能在某些介绍 CSS 网格布局的相关教程中有看到 display 取值 subgrid。这里要告诉大家的是,在写这篇文章的时候,subgrid 值已从 display 属性值列表中删除。

在 CSS 中创建网格容器是使用 display 的值来创建的,有关于 display 更深入的介绍,可以阅读《Web布局:display 属性》一文。

网格容器的尺寸

网格容器的尺寸是使用它所参与的格式化上下文的规则来确定的:

  • 作为一个块格式化上下文中的块级框,它的尺寸与建立格式化上下文的块级框一样,与非替换的块级框一样计算自动内联尺寸
  • 作为一个内联格式化上下文中的内联级框,它的尺寸与原子内联级框(内联块)一样

在内联和块格式化上下文中,网格容器的自动块尺寸是其最大内容(max-content)的大小。

一个网格容器的最大内容尺寸(max-content)或最小内容尺寸(min-content)是该网格容器在适当的轴上的轨道尺寸(包括网格沟槽)的总和。

简单地来说,网格容器的尺寸可以像其它元素容器一样,使用尺寸相关的属性(比如 widthmax-widthmin-widthheightmax-heightmin-height与及其对应的逻辑属性)来设置。比如下面这个示例:

.grid__container {
    --grid: grid;
    --width: 40;
    --height: 30;
    display: var(--grid);
    grid-template-columns: repeat(3, 200px);
    gap: 10px;
    width: calc(var(--width) * 1vw);
    height: calc(var(--height) * 1vh);
    overflow: auto;
}

就该示例而言,我们在网格容器上显式设置了 widthheight(拖动滑块可以动态调整它们的值),同时使用 grid-template-columns 指定了每列列宽是 200px,每行行高根据网格项目自身高度尺寸来决定。这样一来,在拖动滑块时,网格容器的 width 值有可能小于三列加沟槽的总和(此例是 320px),也有可能大于它们的总和:

  • width 小于 320px 时,网格容器会出现水平滚动条(因为容器显式设置了overflow: auto
  • width 大于 320px 时,网格容器会有空白空间留出

网格容器的高度和宽度类似的,只不过没有显式使用 grid-template-rows 来显式指每行的行高,而是由网格项目盒模型自身决定。你将看到的效果如下:

除此之外,我们还可以通过 grid-template-columnsgrid-template-rows 以及 gap 等属性来控制网格容器的尺寸:

.grid__container {
    --col-1: 100;
    --col-2: 100;
    --col-3: 100;
    --row-1: 50;
    --row-2: 50;
    --row-3: 50;
    display: grid;
    grid-template-columns:
        calc(var(--col-1) * 1px)
        calc(var(--col-2) * 1px)
        calc(var(--col-3) * 1px);
    grid-template-rows:
        calc(var(--row-1) * 1px)
        calc(var(--row-2) * 1px)
        calc(var(--row-3) * 1px);
    gap: 10px;
}

拖动示例中的滑块,可以看到网格容器尺寸的变化:

网格项目的尺寸

在网格布局中,除了 grid-template-columnsgrid-template-rowsgrid-template-areas 以及 grid-columngrid-rowgrid-area 等属性可以决定网格项目尺寸之外,还有我们熟悉的设置元素尺寸的相关属性,比如《图解CSS: 元素尺寸的设置》一文中提到的 widthheight 等属性。

除此之外,网格项目的尺寸还会受设置在网格项目上的对齐属性,比如 align-selfjustify-self的影响。有关于这方面的更详细的介绍,我们放到后面介绍网格中的对齐方式一节中来介绍。

定义网格

元素显式设置 display 的值为 gridinline-grid 只是帮助我们创建了一个网格容器(格式化上下文而以)。但定义一个网格还是需要依赖于其他的一些属性,比如前面示例中多次出现的 grid-template-columnsgrid-template-rowsgrid-template-areas,还有 grid-auto-rowsgrid-auto-columns等属性。根据不同的属性,我们定义的网格又分为 显式网格隐式网格 。其中 grid-template-columnsgrid-template-rowsgrid-template-areas 三个属性定义的网格被称为 显式网格grid-auto-rowsgrid-auto-columns 定义的网格被称为 隐式网格

定义显式网格:grid-template-columns/rows/areas

在 CSS 网格布局,如果在网格容器中显式使用了 grid-template-columnsgrid-template-rowsgrid-template-areas 三个属性指定了网格轨道,那么这个网格就被称为 显式网格

grid-template-rowsgrid-template-columns

先来看 grid-template-rowsgrid-template-columns

我们可以在 grid-template-rowsgrid-template-colums 属性上设置用空格分隔开来的多个数值,这些数值列表定义了网格的行和列。这些值同时代表网格轨道的大小,它们之间的空格代表网格线。这两个属性可接受的值:

grid-template-columns: none | <track-list> | <auto-track-list>
grid-template-rows: none | <track-list> | <auto-track-list>

其中 none 是其初始值,表示此属性不创建显式网格轨道(尽管显式网格轨道仍可由 grid-template-areas 创建)。

注意,在没有显式网格的情况下,任何行和列都将被式生成,它们的大小将由 grid-auto-rowsgrid-auto-columns 属性决定。

<track-list> | <auto-track-list> 指将网格轨道列表指定为一系列的轨道尺寸函数和网络线名称。每个轨道尺寸函数都可以被指定为一个长度(<length>)、网格容器大小的百分比(<percentage>)、占据列或行的内容的测量值(内容的宽高),或者网格中自由空间的一部分(即 fr 指定的轨道尺寸)。也可以使用 minmax() 函数指定一个范围值,它可以结合之前提到的任何机制,为列或行指定单独的最小和最大轨道尺寸。

轨道列表(Track List)的语法规则如下:

<track-list>          = [ <line-names>? [ <track-size> | <track-repeat> ] ]+ <line-names>?
<auto-track-list>     = [ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>? <auto-repeat>
                        [ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>?
<explicit-track-list> = [ <line-names>? <track-size> ]+ <line-names>?

<track-size>          = <track-breadth> | minmax( <inflexible-breadth> , <track-breadth> ) | fit-content( <length-percentage> )
<fixed-size>          = <fixed-breadth> | minmax( <fixed-breadth> , <track-breadth> ) | minmax( <inflexible-breadth> , <fixed-breadth> )
<track-breadth>       = <length-percentage> | <flex> | min-content | max-content | auto
<inflexible-breadth>  = <length-percentage> | min-content | max-content | auto
<fixed-breadth>       = <length-percentage>
<line-names>          = '[' <custom-ident>* ']'

看到上面的语法规则是不是有点晕。莫急,我们通过一些简单的示例帮助大家理解它们。

在网格容器上显式设置 grid-template-columns

.grid__container {
    grid-template-columns: 180px 20% auto 1fr 10vw;
}

示例使用 grid-template-columns: 180px 20% auto 1fr 10vw 将网格容器分成了五列,其值分别是每列的列宽:

grid-template-columns 的值有各种不同单位的值,比如 px%vw,有关键词 auto,还有网格布局中独有的单位( <flex> 单位)fr。后面我们将会花一些篇幅来介绍它们在网格中的使用。回到这个示例中来,从效果上你可能发现了,虽然只在网格容器上使用了 grid-template-columns 定义了一个五列的网格,但事实效果是一个五列两行的网格,这是因为网格容器中有十个网格项目,但只定义了五列,那么第六个网格项目就会自动流到新的一行,即第二行,并且以网格项目的高度来定义计算每行的高,相当于 grid-template-rows 设置了值 auto

我们在上面的示例的基础上显式添加 grid-template-rows

.grid__container {
    grid-template-columns: 180px 20% auto 1fr 10vw;
    grid-template-rows: 25% auto;
}

这样一来,grid-template-columnsgrid-template-rows 就一起定义了一个五列两行的网格,和前面示例不同的是,指定了行的高度:

这两个属性的值除了 auto 关键词之外,还可以接受以下 CSS 函数 和关键词:

  • fit-content(<length-percentage>)
  • minmax(min, max)
  • max-contentmin-content

比如下面这个示例,我们在 grid-template-columns 中使用这几个函数,创建了一个四列网格。第一列是该轨道的最小内容尺寸(min-content),第二列是该轨道的最大内容尺寸(max-content),第三列如果内容大于10rem(即fit-content(10rem))的话该列宽度为 max-content,第四列是 minmax(10rem, 1fr) ,一个 10rem ~ 1fr 之间的范围值,大于等于 min10rem)值,并且小于等于 max1fr)值,如果 max 值小于 min 值,则该值会被视为 min值:

.grid__container {
    grid-template-columns: min-content max-content fit-content(10rem) minmax(10rem,1fr);
}

示例中的每列会根据网格项目的最小内容,最大内容以及网格内容等决定实际列宽:

我们还可以在这两个属性中,使用 <flex> 单位 frrepeat() 来创建等宽的列和等高的行。比如我们创建一个等分的三行四列的网格,可以像下面这样创建:

.grid__container {
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(3, 1fr);
}

上面的代码等同于:

.grid__container {
    grid-template-columns: 1fr 1fr 1fr 1fr;
    grid-template-rows: 1fr 1fr 1fr;
}

该示例把网格容器的宽度均分成四等份,高度均分为三等份,每列的宽相等,每行的高相等:

repeat() 函数还有一个特性,可以把多列(或多行)重复性用该函数来描述,比如说第一行高度是 12rem,第二行高度是 1fr,第三行高度是 12rem,第四行高度是 1fr,我们就可以在 grid-template-rows上像下面这样使用repeat()函数:

grid-template-rows: repeat(2, 12rem 1fr);

// 等同于
grid-template-rows: 12rem 1fr 12rem 1fr;

另外,还可以将 repeat()minmax()auto-fillauto-fit 关键词结合在一起使用,比如:

grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));

比如下面这个示例,我们将这几个功能都结合在一起:

.grid__container {
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    grid-template-rows: repeat(2, 12rem 1fr);
}

minmax() 函数中使用 auto-fitauto-fill 最终得到的效果是不相同的,这里暂不详细介绍,同样放到后面来阐述。

当你创建一个网格容器时,网格轨道之间会自动创建网格线,且会自动分配正负数。使用 grid-template-columnsgrid-template-rows 时可以显式的给网格线命名,不过要注意它的使用语法, 网络线名称放在中括号内 ,我们可以像下面这样使用:

.grid__container {
    grid-template-columns: [col1-start] 180px [col2-start] auto [col3-start] 30% [col3-end];
    grid-template-rows: [row1-start] 180px [row2-start] auto [row3-start] 1fr [row3-end];
}

使用浏览器开发者工具,我们可以选择网格线的命名(字符串)或网格线索引号显示,两者对应关系如下图所示:

如果在 grid-template-columnsgrid-template-rows 中显式命名网格线名称的话,可以给同一条网格线命名一个以上的名称,比如下面示例中的第 2 条列网格线,同时命名成 [col1-end col2-start],这样命名也好理解,这条网格线既是第一列的结束网格线也同时是第二列的起始网格线:

.grid__container {
    grid-template-columns: [col1-start] 180px [col1-end col2-start] 20% [col2-end col3-start] 1fr [col3-end col4-start] 10vw [col4-end];
    grid-template-rows: [row1-start] 200px [row1-end row2-start] 1fr [row2-end];
}

如果在同一条网格线上命名两个名称的话,在开发者工具中显示网格线的时候,也会把两个名称显示出来:

要是定义的网格每列(或每行)尺寸都相等时,会使用 repeat() 函数让定义网格变得更简易。这种方式在前面有向大家演示过,这里要说的是,我们同样可以将网格线的命名用于 repeat() 函数中,比如:

.grid__container {
    grid-template-columns: repeat(4, 1fr [col]);
}

// 等同于
.grid__container {
    grid-template-columns: 1fr [col] 1fr [col] 1fr [col] 1fr [col];
}

用开发者工具显示命名的网格线名称时,你会发现从第二条至最后一条,每条网格线名称都是 col 。另外这个示例并未grid-template-rows 中显式命名网格线名称,因此在行网格线只能看到数字索引号(网格线):

值得注意的是,使用 grid-template-columnsgrid-template-rows 定义网格时,不能缺少网格轨道尺寸(<track-size>)的设置,否则会被视为语法错误,相当于未显式使用这两个属性定义网格。

.grid__container 
    grid-template-columns: [col1-start] [col1-end col2-start] [col2-end col3-start] [col3-end];
    grid-template-rows: [row1-start] [row1-end row2-start] [row2-end row3-start] [row3-end];
}

我们花了很长篇幅介绍了 grid-template-columnsgrid-template-rows 的使用,这两个属性涉及到的值方式很多。这里简单小结一下。前面列出 <track-list> | <auto-track-list> 时有很多种不同类型的选项,并且都在示例中向大家呈现了,这里简单的阐述一下它们的含义:

  • <line-names> : 网格线名称,指的是显式命名的网格线名称,即 '[' <custom-ident>* ']'
  • <track-size> : 轨道尺寸(行高或列宽),可以是 <length-percentage> 值, <flex>(即带 fr 单位的值),也可以是一些关键词(比如 min-contentmax-contentauto),也可以是 minmax()fit-content() 函数
  • <track-repeat> : 即 repeat() 函数,该函数可以传递两个参数,第一个参数是重复的数量(<integer [1,∞]>),第二个参数是轨道尺寸(<track-size>),也可以同时是轨道尺寸和网格线名称(<track-size> + <line-names>
  • <fixed-size> : 可以是 <length-percentage>minmax()
  • <fixed-repeat> :有点类似于 <track-repeat>,不同的是第二个参数是 <fixed-size> + <line-names>
  • <inflexible-breadth> :它的值类型主要有 <length-percentage>min-contentmax-contentauto

从这些值类型也能发现,grid-template-columnsgrid-template-rows 属性取值的类型是多么的灵活,这也从侧面也告诉大家,grid-template-columnsgrid-template-rows 因为过于灵活,也造成理解成本更大,也过于复杂。不过,我们可以将其简化一下,掌握下面这些值的使用规则就可以掌握好这两个属性的使用:

  • <length> : 非负值的长度大小,比如100px10rem30vw 等长度值
  • <percentage> : 非负值的百分比值,它相对于网格容器宽度计算。采用百分比时有一个细节需要注意,如果网格容器的尺寸大小依赖网格轨道大小时,百分比值将被视为 auto
  • <flex> : 非负值,用单位 fr 来定义网格轨道大小的弹性系数,有点类似于 Flexbox 布局中的 flex-grow 属性,按比例分配网格容器的剩余空间。如果 fr 用于 minmax() 函数中时,它将是一个自动最小值,即可 minmax(auto, <flex>)
  • max-content : 是一个用来表示以网格项的最大的内容来占据网格轨道
  • min-content : 是一个用来表示以网格项的最大的最小内容来占据网格轨道
  • minmax(min, max) : 是一个来定义大小范围的函数,大于等于 min 值,并且小于等于 max 值。如果 max 值小于 min 值,则该值会被视为 min 值。最大值可以设置为网格轨道系数值 <flex>,但最小值则不行
  • auto : 如果轨道为最大时,等同于 <max-content>,为最小时,则等同于 <min-content>
  • fit-content([<length> | <percentage>]) : 相当于 min(max-content, max(auto, argument)),类似于 auto 的计算(即 minmax(auto, max-content)),除了网格轨道大小值是确定下来的,否则该值都大于 auto 的最小值
  • repeat([<positive-integer> | auto-fill | auto-fit], <track-list>) : 表示网格轨道的重复部分,以一种更简洁的方式去表示大量而且重复列的表达式

简而之,这些都是用来确定网格轨道大小的方法。后面将会花一节内容专门和大家探讨,这主要是因为定义网格轨道(行或列)有很多选项和单位可以选择,只有掌握了什么时候应该使用什么,才能更好的定义好网格。

特别声明grid-template-columnsgrid-template-rows 两属性分别在 Level 2Level 3 两个规范围中新增了 subgridmasonry 两个属性,可用来创建子网格和瀑布流布局。这两个属性在 CSS 网格布局中是新增的特性,非常的强大,也很实用,能帮助我们实现很多复杂的布局效果。因此,后面会把这两个特性单独拿出来和大家探讨。感兴趣的同学,可以关注一下,或者直接跳到这两个章节。

grid-template-areas

创建显式网格,除了 grid-template-columnsgrid-template-rows 之外还可以使用 grid-template-areas 属性,该属性也是运用于网格容器上,但相对于 grid-template-columnsgrid-template-rows 属性要简单地多。grid-template-areas 语法规则很简单:

grid-template-areas: none | <string>+

grid-template-areas 属性指定了命名的网格区域,它们不与任何特定的网格项目相关联,但是它们可以和一些网格定位属性关联起来,比如 grid-row-startgrid-row-endgrid-column-startgrid-column-end,也可双和一些简写属性关联起来,比如 grid-rowgrid-columngrid-areagrid-template-areas 属性的语法还提供了网格结构的可视化,使网格容器的整体布局更容易理解。该属性的主要接受的值有:

  • none :表示没有命名的网格区域,同样也没有显式的网格轨道被这个属性定义(尽管显式的网格轨道仍然可以由 grid-template-columsgrid-template-rows 创建)
  • <string> :为grid-template-areas 属性列出的每一个单独的字符串创建一行,一个字符串中用空格分隔的每一个单元会创建一列。多个同名的,跨越相邻行或列的单元格称为网格区域。非矩形的网格区域是无效的。

简单地说,grid-template-areas 中的每个字符串值都代表网格中的一个行。每个字符串中以空格分隔的一组值代表网格中的列。这些字符串中的每个网格项目的名称都被映射到特定的 HTML 元素。我们先来看一个简单的示例,使用 grid-template-areas 创建一个常见的三列的页面布局效果:

这个布局有 headerbrandasidesidebarmainfooter 几个区域。其对应的 HTML 结构如下:

<!-- HTML -->
<div class="container">
    <header>Header Section</header>
    <div class="brand">Brand Section</div>
    <aside>Aside Section</aside>
    <main>Main Section</main>
    <div class="sidebar">Sidebar Section</div>
    <footer>Footer Section</footer>
</div>

.container元素中使用grid-template-areas像下面这样定义一个网格:

.container {
    display: grid;
    grid-template-areas:
            "header header header header header header header header header header header header"
            "brand brand brand brand brand brand brand brand brand brand brand brand"
            "aside aside aside main main main main main main sidebar sidebar sidebar"
            "footer footer footer footer footer footer footer footer footer footer footer footer";
}

上面的代码定义了一个124行的网格:

上图中实心蓝框框区的区域就是grid-template-areas定义的网格区域,但离我们所需要的布局效果距有较大的差异,并未达到所需的布局效果。我们需要在对应的 HTML 元素上使用grid-area引用grid-template-areas已命名的网格区域的名称:

header {
    grid-area: header;
}
.brand {
    grid-area: brand;
}
aside {
    grid-area: aside;
}
main {
    grid-area: main;
}
.sidebar {
    grid-area: sidebar;
}
footer {
    grid-area: footer;
}

虽然使用grid-area属性将对应的HTML元素放置到相应的网格区域中,但你也可能发现了,网格的列宽和行高是自动计算的,为了让布局更合理一些,指定相应的列宽和行高,可以在上面的代码中添加grid-template-columnsgrid-template-rows

.container {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    grid-template-rows: 80px 160px 1fr 60px;
    grid-template-areas:
        "header header header header header header header header header header header header"
        "brand  brand  brand  brand  brand  brand  brand  brand  brand  brand  brand  brand"
        "aside  aside  aside  main   main   main   main   main   main   sidebar sidebar sidebar"
        "footer footer footer footer footer footer footer footer footer footer footer footer";
}

最终效果如下:

示例网格线、网格区域,网格轨轨道相关的信息如下图所示:

使用grid-template-areas定义网格区域是有一些规则的。如果违反这些规则将使数值无效,因此你的布局将无法实现。第一条规则是:

规则① :必须描述一个完整的网格,即网格上的每一个单元格都必须被填充。

比如:

.grid__container {
    grid-template-areas:
        "header  header header header"
        "nav     main   main   aside"
        "footer footer footer footer";
}

每个命名对应的就是一个网格单元格:

第二条规则:

规则② :一连串的空白,代表什么都没有,将造成grid-template-areas语法错误!

即,使用一连串空格来代表一个命名区域。比如说,在上面的示例基础上,将最后一个header 有一连串的空格符来替代,即缺少一个:

.grid__container {
    grid-template-areas:
        "header  header header      "
        "nav     main   main   aside"
        "footer  footer footer footer";
}

这将造成grid-template-areas语法错误:

第三条规则:

规则③ :在网格命名中可以使用一个或多个.U+002E),代表一个空单元格。

我们使用“ 规则① ”已经实现了用区域填充网格,不留空余空间,而且也不能使用“ 规则② ”来给网格留出空的网格单元,因为“规则②”会造成grid-template-areas的语法错误。如果想给网格留出空白的单元格,可以使用一个点(.)或多个点(...)来表示。比如说,我们希望把footer区域和main左右对齐,即footer左边留出nav区域,同时右边留出aside区域,就可以像下面这样使用.来表示:

.grid__container {
    grid-template-areas:
        "header    header    header   header"
        "nav       main      main     aside"
        ".         footer    footer   .";
}

效果如下:

使用开发者工具查看的网格如下图所示:

很多时候为了让布局更整齐,还可以使用多个“.”符号,如果在多个点符号之间没有任何空格,比如 ...,那么它们就会被计算为一个单元格,即.等同...

grid-template-areas:
    "header    header    header   header"
    "nav       main      main     aside"
    ".         footer    footer   .";

// 等同
grid-template-areas:
    "header    header    header   header"
    "nav       main      main     aside"
    "...       footer    footer   ...";

用多个“.”符号表示一个网格单元格,它的好处是对于复杂的布局来说很容易让行列对齐,但需要特别注意,在使用多个点时,点与点之间绝对不能有任何空格符号,一旦有空格,那么将表示独立的网格单元格,比如:

.grid__container {
    grid-template-areas:
        "header    header    header   header"
        "nav       main      main     aside"
        ". .      footer       ...";
}

网格审查结果如下:

第四条规则:

规则④ : 一个命名听单元格命名(令牌)创建一个具有相同名称的命名网格区域。在行内和行间的多个命名的单元格命名(令牌)创建单一的命名的网格区域,横跨相应的网格单元。简单的说,跨行命名相同的网格区域名称,可以达到合并单元格的作用。

比如像下面这个示例,我们需要将侧边栏(sidebar)和页脚(footer)合并起来(mainfooter 区域具有相同的宽度)。

可以像下面这样来命名网格区域:

/* Before */
.container {
    grid-template-areas:
        "header  header"
        "sidebar main"
        "footer  footer"
}

/* After */
.container {
    grid-template-areas:
        "header  header"
        "sidebar main"
        "sidebar footer"
}

你可以尝试着切换上面的单选按钮,查看布局的变化:

如果用浏览器的布局审查器,你可以查看到两者的差异:

使用网格区域命名实现单元格的合并时需要注意一点,“一个命名的网格区域跨越了多个网格单元格,但这些单元格并没有形成一个单一的填充矩形,则声明无效”,比如说一个类似L的形状:

第五条规则:

规则⑤ :任何其他字符的序列,会代表一个垃圾标记(Trash Token),会使声明无效。

使用 grid-template-areas 属性中使用 <string> 命名网格区域时,如果使用一些其他的字符,比如像数字 1#1st 等,将会被视为无效的声明。

但在 grid-template-areas 中可以使用 Emoji 字符:

也可以是纯 HTML 实体符(HTML Symbols),还可以是 Emoji 和 HTML Symbols 混合在一起使用:

虽然这样来命名网格区域能定义网格,但不推荐这么使用。因为命一个有语义的名称要比这些字符更易于理解。

最后一条规则:

规则⑥ :当序列化 grid-template-areas<string> 值的指定值或计算值时,相邻两字符串(网格区域命名)被一个空格(U+0020)隔开,当两者之间有多个空格符时,会被视为一个,其他空格将会被忽略。

比如下面两个grid-template-areas中的字符命最终结果是一样的:

也就是说,在使用grid-template-areas定义网格时,在命名应该遵循这些规则,不然容易造成语法上的错误,使该属性失效:

  • 规则① :必须描述一个完整的网格,即网格上的每一个单元格都必须被填充
  • 规则② :一连串的空白,代表什么都没有,将造成grid-template-areas语法错误
  • 规则③ :在网格命名中可以使用一个或多个.U+002E),代表一个空单元格
  • 规则④ : 一个命名听单元格命名(令牌)创建一个具有相同名称的命名网格区域。在行内和行间的多个命名的单元格命名(令牌)创建单一的命名的网格区域,横跨相应的网格单元。简单的说,跨行命名相同的网格区域名称,可以达到合并单元格的作用
  • 规则⑤ :任何其他字符的序列,会代表一个垃圾标记(Trash Token),会使声明无效

CSS 网格布局中使用 grid-template-areas 指定网格区域,创建了显式的网格,同时也将生成隐式的网格线名。比如说一个网格区域的名称叫 foo,它会对应的生成四个隐式的网络线名,分别分配在该网格区域的四边,即:

  • 网格区域行开始和列开始,被称为 foo-start
  • 网格区域行结束和列结束,被称为 foo-end

这些随着网格区域名称创建的隐式网格线名和其他网格线名一样,只是它们不是使用grid-template-rowsgrid-template-colummns中是式创建的网格线名。而且这些隐式的网格线名同样可以用于grid-rowgrid-column中,用于放置网格项目。比如下面这个示例:

.container {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 80px 1fr 60px;
grid-gap: 1rem;

grid-template-areas:
    "header   header"
    "sidebar  main"
    "footer   footer";
}

header {
    /* ①:  grid-area: header; */
    /* ②: grid-area: header-start / header-start / header-end / header-end; */
    grid-row: header-start / header-end;
    grid-column: header-start / header-end;
}
aside {
    /* ①: grid-area: sidebar;   */
    /* ②: grid-area: sidebar-start / sidebar-start / sidebar-end / sidebar-end; */
    grid-row: sidebar-start / sidebar-end;
    grid-column: sidebar-start / sidebar-end;
}
main {
    /* ①: grid-area: main;   */
    /* ②: grid-area: main-start / main-start / main-end / main-end; */
    grid-rows: main-start / main-end;
    grid-column: main-start / main-end;
}
footer {
    /* ①:  grid-area: footer; */
    /* ②: grid-area: footer-start / footer-start / footer-end / footer-end; */
    grid-rows: footer-start / footer-end;
    grid-column: footer-start / footer-end;
}

grid-area引用网格区域名和引用隐式网格线以及grid-columngrid-row引用隐式网格线来放置网格项目,最终的效果都是一致的:

grid-template-areas声明的网格区域名称,对应创建的隐式网格线如下所示:

grid-template-areas:
    "header   header"
    "sidebar  main"
    "footer   footer";

/* 相当于 grid-template-columns 和 grid-template-rows 像下面这样创建显式网格线 */
grid-template-columns: 
    [header-start sidebar-start footer-start] 200px [sidebar-end main-start] 1fr [header-end main-end footer-end];
grid-template-rows: 
    [header-start] 80px [header-end sidebar-start] 1fr [sidebar-end footer-start] 60px [footer-end];

而且我们显式在grid-template-columnsgrid-template-rows 显式设置了网格线名称,对于grid-template-areas隐式创建的网格线名称也不会有任何的影响,只会在隐式的网格线名称上新增显式声明的网格线名称:

.container {
    display: grid;
    grid-template-columns: 
        [hd-col-start sb-col-start ft-col-start] 200px [sb-col-end mn-col-start] 1fr [hd-col-end mn-col-end ft-col-end];
    grid-template-rows: 
        [hd-row-start] 80px [hd-row-end sb-row-start] 1fr [sb-row-end ft-row-start] 60px [ft-row-end];
    grid-gap: 1rem;

    grid-template-areas:
        "header   header"
        "sidebar  main"
        "footer   footer";
}

反过来,如果在grid-template-columnsgrid-templae-rows中按grid-template-areas隐匿创建网格线方式来声明网格线名称,即显式添加相同形式的命名网格线名称(foo-start/foo-end),则会有效的创建一个命名为foo的网格区域,只不过这种方式创建的网格区域是一个隐式的网格区域,也就是没有在grid-template-areas中显式声明网格区域。隐式创建的网格区域同样可以在网格项目上使用grid-area引用对应的网格区域来放置。比如下面这个示例:

.container {
    display: grid;
    grid-gap: 1rem;

    grid-template-columns: 
        [header-start sidebar-start] 200px [sidebar-end main-start footer-start] 1fr [header-end main-end footer-end];
    grid-template-rows: 
        [header-start] 80px [header-end sidebar-start main-start] 1fr [main-end footer-start] 60px [sidebar-end footer-end];
}

header {
    grid-area: header;
}
aside {
    grid-area: sidebar;
}
main {
    grid-area: main;
}
footer {
    grid-area: footer;
}

特别声明:示例中用到的grid-rowgrid-columngrid-area是用于网格项目中的属性,其所起作用和如何实用这里暂且不详细阐述,后面在介绍网格项目相关的属性时,会详细介绍。这里你只需要知道他们是用于网格项目,可以根据网格线名称和网格区域名称将网格项目放置到指定位置即可!

grid-template

grid-templategrid-template-rowsgrid-template-columnsgrid-template-areas 三个属性的简写属性,该属性语法规则如下:

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

可设值:

  • none :即恢复默认设置。行列隐式生成,grid-auto-rowsgrid-auto-columns 决定网格轨道尺寸
  • <grid-template-rows> / <grid-template-columns> : 指定 grid-template-rowsgrid-template-columns 之值,并设grid-template-areas的值为none
  • [<line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]? :将 grid-template-areas设置为所列的字符串(<string>);将<grid-template-columns> 设置为 斜线/<explicit-track-list>轨道列表(如果没有指定,则为0);将grid-template-rows 设置为每个字符串后面的 <track-size>(默认为 auto),并在每个尺之前(或之后)接上定义的命名的网格线名称

我们来看两个简单地示例,来增强对 grid-template 属性的理解。先来看 grid-template 取值为 <grid-template-rows> / <grid-template-columns>

.container {
    grid-template: 80px 1fr 60px / 1fr min(75ch, 80vw) 1fr;
}

/* 等同于 */
.container {
    grid-template-rows: 80px 1fr 60px;
    grid-template-columns: 1fr min(75ch, 80vw) 1fr;
    grid-template-areas: none;
}

使用网格审查器查看布局结果:

再来看更复杂一点的,就是grid-template包括了grid-template-rowsgrid-template-columnsgrid-template-areas。基于上面的示例,我们可以像下面这样使用grid-template

.container {
    grid-template:
        [header-start] "header header header" 80px [header-end]
        [nav-start main-start sidebar-start] "nav    main   sidebar" 1fr [nav-end main-end sidebar-end]
        [footer-start] "footer footer footer" 60px [footer-end]
        / 
        1fr min(75ch, 80vw) 1fr;
}

/* 等同于 */

.container {
    grid-template-areas:
        "header header header"
        "nav    main   sidebar"
        "footer footer footer";
    grid-template-rows:
        [header-start]  80px [header-end nav-start main-start sidebar-start]  1fr [nav-end main-end sidebar-end footer-start]  60px [footer-end];
    grid-template-columns: 1fr min(75ch, 80vw) 1fr;
}

grid-template简写属性中,grid-template-columns也可以对应加上网格线的命名:

.container {
    grid-template:
    [header-start] "header header header" 80px [header-end]
    [nav-start main-start sidebar-start] "nav    main   sidebar" 1fr [nav-end main-end sidebar-end]
    [footer-start] "footer footer footer" 60px [footer-end]
    / [header-start nav-start footer-start] 1fr [nav-end main-start] min(
    75ch,
    80vw
    )
    [main-end sidebar-start] 1fr [header-end sidebar-end footer-end];
}

/* 等同于 */
.container {
    grid-template-areas:
        "header header header"
        "nav    main   sidebar"
        "footer footer footer";
    grid-template-rows: 
        [header-start]  80px [header-end nav-start main-start sidebar-start]  1fr [nav-end main-end sidebar-end footer-start]  60px [footer-end];
    grid-template-columns:
        [header-start nav-start footer-start] 1fr [nav-end main-start] min(75ch,80vw) [main-end sidebar-start] 1fr [header-end sidebar-end footer-end];
}

效果都是一样的:

两种写法的网格线命名差异如下:

注意 : 在这些轨道列表中不允许使用 repeat() 函数,因为轨道的目的是在视觉上与“ASCII艺术(ASCII Art)”中的行、列一对一对排列。

另外有一点需要注意,使用简写的 grid-template 属性时不会重置隐含的网格属性(grid-auto-columnsgrid-auto-rowsgrid-auto-flow),不过后面我们要介绍的另一个简写属性 grid 会包含这几个属性。

在实际使用中,不管是对网格布局熟悉的同学,还是初学者,就我个人而言,不建议使用grid-template(或grid)属性,因为他过于复杂,除了不易于理解,还很容易出错。换句话说,如果你不是非常熟悉他的话,请使用 grid-template-areasgrid-template-rowsgrid-template-columns 三个属性。

如果你阅读到这里的话,我想你知道怎么使用 grid-template-areasgrid-template-rowsgrid-template-columns(以及它们的简写属性 grid-template)来定义 显式网格,但这仅是第一步。因为这里面还涉及很多其他的知识,比如网格轨道尺寸的设置、网格线的命名等。那么接下来,我们花一些篇幅来介绍使用grid-template-rowsgrid-template-columns定义网格轨道(行或列)怎么来做选择,因为使用它们创建网格轨道有很多选项和单位可以选择,只有掌握了什么时候应该使用什么,才能更好的定义好网格。

待续 ...

这篇文章主要介绍了如何使用 display 来创建网格容器,以及 使用 grid-template-areasgrid-template-columnsgrid-template-rows 以及它们的简写属性grid-template定义显式网格。这仅仅是可用于网格容器的常见的几个属性。在介绍grid-template-areasgrid-template-columnsgrid-template-rowsgrid-template 定义显式网格时,提到grid-template-rowsgrid-template-columns定义网格轨道可选项和单位比较丰富,只有掌握了什么时候应该使用什么,才能更好的定义好网格。那么在下一节中将围绕着这个主题展开。如果你感兴趣的话,请继续关注后续的相关更新。

返回顶部