图解CSS: Grid布局(Part3)

发布于 大漠

在第二部分中主要介绍了如何使用display来创建网格容器和网格项目,以及怎么使用 grid-template-columnsgrid-template-rowsgrid-template-areas 定义显式网格。在这部分(Part3)将和大家探讨如何设置网格轨道尺寸?简单地说,在grid-template-columnsgrid-template-rows 中可以显式设置网格轨道大小(列宽和行高),而且设置大小的方式有很多种方式,比如使用不同的长度单位,使用关键词,使用CSS函数等来设置网格轨道大小。如果你感兴趣的话请继续往下阅读。

网格轨道尺寸的设置

CSS 网格布局中轨道的设置有很多种选择,也正如此,我们在定义网格轨道的时候应该掌握什么时候使用什么?接下来我们就围绕着这个话题来展开。

从前面的内容中可以知道,在网格布局中可以使用 grid-template-columnsgrid-template-rows为显式的设置网格轨道。在这两个属性中我们可以设置不同的值,大致有三种类型的值:

  • 带有不同单位的的长度值,比如我们熟悉的 pxemremvwvh%,不怎么常用的chex,还有网格布局中独有的单位fr
  • 关键词,比如noneautomin-contentmax-content
  • CSS函数,比如fit-content()minmax()repeat()min()max()clamp()

我们先从熟悉的单位开始。

px

从《图解CSS:CSS 的值和单位》一文中,可以得知,px是一个固定单位,也是常用的一个<length>值单位。只要你需要设置一个固定尺寸的轨道时,就应该使用px单位。假设我们要构建一个固定宽度的三列布局(如200px520px200px),可以像下面这样使用:

.container {
    display: grid;
    gap: 20px;

    grid-template-columns: 200px 520px 200px;
}

基于上面这个示例的方式,我们可以非常容易的构建出类似于960gs的固定网格:

/*
* 1. Container's width is 960px
* 2. Gutter's width is 20px
* 3. Column's width is 60px
* 4. Left padding of the container is 10px
* 5. Right padding of the container is 10px
* width = 60 x 12 + (12 - 1) * 20 + 10 + 10 = 960
*/
.container {
    width: 960px;
    padding-left: 10px;
    padding-right: 10px;
    display: grid;
    gap: 20px;

    grid-template-columns: 60px 60px 60px 60px 60px 60px 60px 60px 60px 60px 60px 60px;
}

如果你在所有的轨道中的值都使用px的话,那该布局就是一个固定布局,不具适应性(不会有很好的响应)。

rem

使用rem单位来设置网格轨道和px有点相似,不同的是rem单位他会根据<html>元素的font-size来确定大小。也就是说,使用rem时,网格轨道大小是根据<html>元素的font-size来确定大小。默认情况之下,<html>font-size16px,那么1rem就等于16px。而且,当font-size调整大小时,网格轨道大小也会随着其大小做出相应的调整:

html {
    --font-size: 16px;
    font-size: var(--font-size);
}

.grid__container {
    display: grid;
    gap: 1rem;
    grid-template-columns: 10rem 15rem 20rem;
}

尝试着拖动示例中的滑块改变<html>font-size的值,会看到网格轨道在小也会随着改变:

在CSS中,rem单位是创建响应式布局的法宝,可以在用户的设备变宽时增加字体大小,比如 《使用Flexible实现手淘H5页面的终端适配》介绍的适配方案,就是在不同宽度的设备会调整<html>font-size的值。换句话说,网格轨道中使用rem结合媒体查询调整(在不同条件下)<html>font-size可以调整网格轨道尺寸,比如下面这个示例:

/* Small devices (landscape phones, 576px and up) */
@media (min-width: 576px) {
    html {
        font-size: 12px;
    }
}

/* Medium devices (tablets, 768px and up) */
@media (min-width: 768px) {
    html {
        font-size: 32px;
    }
}

/* Large devices (desktops, 992px and up) */
@media (min-width: 992px) {
    html {
        font-size: 40px;
    }
}

/* X-Large devices (large desktops, 1200px and up) */
@media (min-width: 1200px) {
    html {
        font-size: 50px;
    }
}

/* XX-Large devices (larger desktops, 1400px and up) */
@media (min-width: 1400px) {
    html {
        font-size: 32px;
    }
}

.grid__container {
    display: grid;
    gap: 0.5rem;
    grid-template-columns: 5rem 5rem 5rem 5rem;
}

调整浏览器视窗宽度大小,会使用对应媒体查询条件下的font-size,相应的网格轨道尺寸也会相应调整:

除此之外,使用CSS的比较函数clamp()函数,不依赖CSS媒体查询,使<html>font-size选择合适的值,比如:

html {
    font-size: clamp(16px, 5vw, 50px);
}

甚至可以根据《CSS中的动态计算》提供的 CSS锁方案,可以变得更为灵活:

:root {
    /* 840px相对于16px计算出来的rem值  */
    --maxViewportWidth: 52.5; 

    /* 360px相对于16px计算出来的rem值  */
    --minViewportWidth: 22.5;

    /* 最小字号font-size的 rem值  */  
    --minFontSize: 1;

    /* 最大字号font-size的 rem值 */
    --maxFontSize: 3.5;

    --f-slope: (
        (var(--maxFontSize) - var(--minFontSize)) /
        (var(--maxViewportWidth) - var(--minViewportWidth))
    );

    --yAxisIntersection: (
        -1 * var(--minViewportWidth) * var(--f-slope) + var(--minFontSize)
    );

    --clamp: clamp(
        var(--minFontSize) * 1rem,
        var(--yAxisIntersection) * 1rem + (var(--f-slope) * 100vw),
        var(--maxFontSize) * 1rem
    );
}

html {
    font-size: var(--clamp);
}

.grid__container {
    display: grid;
    gap: 0.5rem;
    grid-template-columns: 5rem 5rem;
}

在不同视窗宽度下,htmlfont-size就会相应调整(没有任何媒体查询条件),网格轨道也就相应调整了大小:

em

em 的单位就像 rem 一样,只不过它不是相对于 <html>font-size计算,而是相对于其父容器的font-size来计算:

em 的计算相对于 rem 来说要复杂的多,这也是我不喜欢使用 em 单位的主要原因。不过,em 有着rem的优势,如果元素的其他属性,比如border-widthpaddingwidth等属性的值使用em,那么调整其父元素的font-size时,使用em相关的属性也会相应调整:

也就是说,如果网格轨道的值使用em,也可以达到类似rem的效果,只不过计算方式不同而以。把上面的rem示例换成em单位:

html {
    --font-size: 16px;
    font-size: var(--font-size);
}

.grid__container {
    display: grid;
    gap: 1em;
    grid-template-columns: 10em 15em 20em;
}

调整font-size的值,网格轨道大小也相应被调整了:

视窗单位(vwvhvminvmax

CSS中的视窗单位主要有 vwvhvminvmax四个,这几个单位和前面所说的 pxremem都不一样,因为它们是相于浏览器视窗大小计算:

我们来看一个示例,使用视窗单位给网格轨道设置大小:

.grid__container {
    display: grid;
    gap: 1vmin;
    grid-template-columns: 20vw 30vh 40vmax;
}

调整浏览器视窗大小时,可以看到网格轨道大小也将随之调整,比如下图分别是视窗 1050px x 888px(即1vw=10.50px1vh=8.88px1vmin=8.88px1vmax=10.5px)和 915px x 421px(即1vw=9.15px1vh=4.21px1vmin=4.21px1vmax=9.15px)网格轨道大小:

当你希望网格轨道大小基于浏览器视窗大小时,就可以使用视窗单位,而且这些单位是响应式布局的最佳选择。另外在移动端上的适配,正如《如何在Vue项目中使用vw实现移动端适配》一文所介绍,使用视窗单位做适配也是最佳选择。

百分比 %

CSS中带有百分比单位的值是一种相对值,它的计算值都有相应的参照值。比如《CSS中百分比单位计算方式》中提到的容器的widthheight 属性值为百分比值时:

  • 元素的width属性取%值时,它的计算是相对于元素父容器的width计算
  • 元素的height属性取%值时,它的计算是相对于元素父容器的height计算

在网格布局中,如果网格轨道的值取值为百分比值时,它的计算是相对于网格宽度的 widthheight 来计算的,其中:

  • 列的百分比(即grid-template-columns值取百分比值)是基于网格容器的宽度
  • 行的百分比(即grid-template-rows值取百分比值)是基于网格容器的高度

比如下面这个示例:

.grid__container {
    --width: 60;
    --height: 60;
    width: calc(var(--width) * 1vw);
    height: calc(var(--height) * 1vh);

    display: grid;
    grid-template-columns: 30% 30% 40%;
    grid-template-rows: 50% 50%;
}

尝试着拖动示例中的滑块调整网格容器大小,你将看到网格轨道值的变化:

你或许知道,如果元素的 width 显式设置了100%值,同时该元素显式设置了padding(或border-width)时,在box-sizing值不是border-box时,元素会溢出其容器:

在网格布局中同样会有类似的现象。上面的示例演示了网格轨道取值为百分比时,它是基于网格容器的大小计算,所以它们并不会关心网格容器中其他情况。比如说,在上面的示例上,使用gap属性,给网格的行和列添加一定的间距,比如20px

.grid__container {
    --width: 60;
    --height: 60;
    width: calc(var(--width) * 1vw);
    height: calc(var(--height) * 1vh);

    display: grid;
    gap: 20px;
    grid-template-columns: 30% 30% 40%;
    grid-template-rows: 50% 50%;
}

这个时候,网格项目会溢出网格容器:

正如示例所示,网格项目溢出网格容器:

因此,在网格轨道中使用百分比值是要特别小心,不要让所有网格轨道都取百分比值,因为这样就无法利用gap属性。当然,在某些情况下,网格轨道取%是很有意义的,比如你想保证某条网格轨道在网格容器中占一定比例。另外就是,当fr不能用于网格沟槽的情况下,%也可以让gap有一个良好的值。

chex

chex 常用于排版中的两个单位,这意味着它们的值取决于元素的 font-family。而我们在使用 emrem 单位时,浏览器会根据元素的 font-size 计算它们的值。无论屏幕上显示的是什么字体,浏览器计算出来的值都是相同的。不过,chex 单位提供了更多的灵活性,它们要求浏览器在计算值和应用样式之前要确定好引用的 font-family。因为,元素的font-family的值对chex单位值的计算有直接关系。

在印刷术语中,x高度由字体小写字母的x高度决定。这通常用字母x来衡量,它没有任何上升和下降。字体的font-sizex高度之间的关系可以告诉你很多关于字体的比例。

ex单位的值来自它们所计算的字体上下文的x高度,x高度由font-familyfont-size两个因素决定。换句话说,它们等于特定字体在特定font-size下的x高:

正如上图,font-familyHelvetica Neue 设置的font-size100px,那么1ex大约等于52px

chex相似,但它不依赖于x的高度;而是基于字体的字符,从字体的0字形宽度中提取它们的值,它还随字体而变化。如此一来,就有点随意,而0的宽度通常是对字体的平均字符宽度,这是一个估计值,所以会有点糟糕。

由于ch单位是一个近似等宽的一个单元,因此在设置容器的宽度是特别的有用。比如说,你想让容器显示特定数量的字符串时,就可以使用ch单位。

也就是说,在文本排版的布局场景中使用 CSS 网格来布局,那么chex单位就很适用。比如说,你想让网格轨道或沟槽的大小取决于字体大小,就可以使用ch单位。

.grid__container {
    display: grid;
    grid-template-columns: repeat(3, minmax(35ch, 25vw));
    gap: 2ex;
    font-size: 12px;
}
fr

fr 单位绝对是一个好的单位。在具体介绍fr单位之前,我们先来回顾一下%在CSS网格布局中的使用。当我们在CSS网格布局中将%px结合在一起使用的时候,很容易造成网格项目溢出网格容器,比如:

.container {
    display: grid;
    grid-template-columns: 20% 60% 20%;
    gap: 20px;
}

正如上面示例所示,内容溢出了:

如果我们把上面示例中的20%换成1fr

.container {
    display: grid;
    grid-template-columns: 1fr 60% 1fr;
    gap: 20px;
}

从示例效果中不难发现,网格的第一列和第三列宽度会随着第二列宽度变化而变化:

这就是fr单位所起的作用。回到fr单位中来,MDN是这样描述fr单位的:

fr单位代表网格容器中可用空间的一等份

从这个描述中不难发现,在CSS网格布局中使用fr单位确定尺寸的网格轨道被称为 弹性网格轨道 ,因为它们会根据网格容器剩余空间进行对网格轨道进行弹性缩放,这个有点类似于 Flexbox 布局中的 flex 有点类似。

一般情况之下,网格轨道使用fr单位时,网格容器的可空间会按下面的公式来计算网格轨道尺寸:

注意,公式中所谓的所有网格轨道的弹性系数总和指的是网格的行或列,网格容器可用空间指的是网格块轴或内联轴方向的空间(即网格容器的宽或高)。

我们通过一个简单的示例来阐述fr的最基础的使用:

.grid__container {
    --col-1: 1fr;
    --col-2: 1fr;
    --col-3: 1fr;
    --col-4: 1fr;
    width: 600px;

    display: grid;
    grid-template-columns: var(--col-1) var(--col-2) var(--col-3) var(--col-4);
}

当网格列轨道的尺寸是min-content时,网格容器将会有一定的剩余空间出现:

但是每个列网格轨道都使用1fr时,网格容器可用空间(600px)将分成4等分(1 + 1 + 1 + 1 = 4),每列网格轨道都将分配到网格容器可用空间的一个等份,即 600 ÷ 4 × 1 = 150px

注意,上面这个示例仅仅是 fr 计算的最基础情况之一,事实上它的计算要复杂地多。

fr在网格布局中计算和Flexbox中flex计算是同样的复杂,接下来,基于上面这个示例,调整网格轨道的fr系数值,看看其计算方式的差异。

先把网格第一列的轨道值从1fr调至到2fr,即:

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

这个时候:

  • ①:fr总系数:2 + 1 + 1 + 1 = 5
  • ②:网格容器可用空间是:600px
  • ③:每1fr相当于:600px ÷ 5 = 120px
  • ④:第一列网格轨道占2fr,即 600px ÷ 5 × 2 = 240px,第二列,三列和四列网格轨道都占1fr,即 600px ÷ 5 × 1 = 120px

不知道你是否发现了,这两个示例具有一个共性:每一个fr计算出来的值都大于网格项目的min-content(即大于108px

.container {
    /* 例1 */
    grid-template-columns: 1fr 1fr 1fr 1fr; /* 每1个fr等于 150px */

    /* 例2 */
    grid-template-columns: 2fr 1fr 1fr 1fr; /* 每1个fr等于 120px */
}

当每一个fr计算出来的值小于网格项目的min-content时,就不能再按前面的公式来分配网格容器可用空间了。比如:

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

此时:

  • ①:fr总系数:2 + 2 + 1 + 1 = 6
  • ②:网格容器可用空间是:600px
  • ③:每1fr相当于:600px ÷ 6 = 100px,该值小于网格项目的min-content(即108px

如果按照前面的公式来分配网格容器可用空间的话,应该是:

这个时候:

  • 网格第一列轨道是2fr,即 600px ÷ 6 × 2 = 200px
  • 网格第二列轨道是2fr,即 600px ÷ 6 × 2 = 200px
  • 网格第三列轨道是1fr,即 600px ÷ 6 × 1 = 100px
  • 网格第四列轨道是1fr,即 600px ÷ 6 × 1 = 100px

可实际计算出来的结果并非如此:

就该例而言,第三列和第四列网格轨道计算出来是100px,但其实际宽度是108px(即min-content),这两列多占了16px,因此需要从其他的网格轨道中扣除这部分空间。多出来的空间也需要按fr系数来分配,这个示例会将16px4fr(第一列和第二列fr总和)分配,也就是每个fr4px,如此计算是为了让大于min-content的网格轨道在原分配的空间基础上按fr的比例扣除多出来的空间,即600px ÷ 6 × 2 - (108 - 100) × 2 ÷ (2 + 2) × 2 = 192px

接下来我们继续加戏。在上面的示例基础上添加gap,比如:

.grid__container {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr 1fr;
    gap: 20px;
}

和前面的示例相比,网格容器的可用空间变了,需要在网格容器宽度上(600px)减去所有网格沟槽总和,即 600px - 20px × 3 = 540px。根据前面的介绍,我们可以获知:

  • ①:fr总系数:1 + 1 + 1 + 1 = 4
  • ②:网格容器可用空间是:600px - 20px × 3 = 540px
  • ③:每1fr相当于:540px ÷ 4 = 135px

即每列网格轨道宽度是 135px

除了添加gap之外,还有可能在网格轨道中的某一个或某几个会是非弹性的轨道,即网格轨道采用的是非fr的值,比如:

.grid__container {
    display: grid;
    grid-template-columns: 200px 1fr 1fr 1fr;
    gap: 20px;
}

同样的,网格容器可用空间变了,即600px - 20px × 3 - 200px = 340px,每一个fr对应的值也就变了:

  • ①:fr总系数:1 + 1 + 1 = 3
  • ②:网格容器可用空间是:600px - 20px × 3 - 200px = 340px
  • ③:每1fr相当于:340px ÷ 3 = 113.33px

第二列,第三列和第四列的网格轨道宽度就是 113.33px

使用fr设置网格轨道尺寸时,还可以和其他的单位混合使用,比如:

.grid__container {
    display: grid;
    grid-template-columns: 200px 1fr 1fr 1fr;
    gap: 2%;
}

这个示例是 fr%gap的取值为2%,相对于网格容器宽度600px计算,等于12px)以及 px 混合使用。其每一个fr的计算如下:

  • ①:fr总系数:1 + 1 + 1 = 3
  • ②:网格沟槽是 2%,相当于600px × 2% = 12px,所有沟槽总和为 12px × 3 = 36px
  • ③:网格容器可用空间是:600px - 600px × 2% × 3 - 200px = 364px
  • ④:每1fr相当于:364px ÷ 3 = 121.33px

第二列,第三列和第四列的网格轨道宽度就是 121.33px

前面这些个阐述fr的示例,都是大于或等于1的,其实在网格布局中使用fr设置弹性网格轨道尺寸时,也可以取0fr ~ 1fr之间的值,只不过取这个范围的值渲染出来的行为有点特殊: 当所有网格轨道弹性系数(fr)之和小于1时,它们将占用小于100%的网格可用空间

从前面的内容中我们可以获知,一个轨道的弹性值实际上是对某种比例的网格容器可用空间的请求,记住,1fr相当于100%的网格容器的可用空间 ;那么,如果该轴中的轨道弹性值请求总和大于100%时,请求就会重新平衡,以保持相同的比例,但正好用完100%的网格容器的可用空间。然而,如果轨道的弹性值总各小于1(全额),比如三个网格列轨道各为.25fr,每个列网格轨道得到网格容器可用空间的25%,最后还会有25%的网格容器可用空间未用完。

.grid__container {
    display: grid;
    grid-template-columns: .25fr .25fr .25fr;
    gap: 20px;
}

来看其计算过程:

  • ①:fr总系数:.25 + .25 + .25 = .75
  • ②:网格沟槽总和是 20px × 2 = 40px
  • ③:网格容器可用空间是:600px - 20px × 2 = 560px
  • ④:每.25fr计算出来的值就是 560px × .25 = 140px
  • ⑤:网格容器剩余空间是:560px - 140px × 3 = 140px

如此一来,该示例的每列网格轨道的宽度都是 140px,网格容器剩余空间是 140px

在这种情况之下,网格容器的剩余空间计算是较为复杂的。我们换一种简单的方式来演示。在上面的示例上新增一列网格轨道,并且将其设置为 auto

.grid__container {
    display: grid;
    grid-template-columns: 0.25fr 0.25fr 0.25fr auto;
    gap: 20px;
}

它的计算过程如下:

  • ①:fr总系数:.25 + .25 + .25 = .75
  • ②:网格沟槽总和是 20px × 3 = 60px
  • ③:第四列网格轨道设置的值是auto,即是该网格项目的内容宽度(min-content),该例是108px
  • ④:网格容器可用空间是:600px - 20px × 3 - 108px = 432px
  • ⑤:每.25fr计算出来的值就是 432px × .25 = 108px
  • ⑥:网格容器剩余空间是:432px - 108px × 3 = 108px

网格容器剩余的空间108px就给了第四列网格轨道,此时其宽度为 108px + 108px = 216px

第四列取值为auto有一点特殊性,如果将第四列的网格轨道值设置为固定值的话,网格容器剩余空间并不会分配给第四列网格轨道:

.grid__container {
    --col: 260px;
    display: grid;
    grid-template-columns: 0.25fr 0.25fr 0.25fr var(--col);
    gap: 20px;
}

其计算过程和前面是相似的。你可以尝试着拖动示例中的滑块改变第四列网格轨道的大小,该值变大时,网格容器的可用空间就会变小,弹性轨道尺寸也会随着变小,小到其最小内容(min-content),反之就会变大:

如果不是特别需求,不建议使用小于1fr,例如,使用1fr2fr通常比使用.33fr.67fr要好,因为如果添加或删除网格轨道,它们更有可能按照预期行事。

最后再来看跨列的表现:

.grid__container {
    --col-1: 1fr;
    --col-2: 1fr;
    --col-3: 1fr;
    --col-4: 1fr;
    width: 602px;

    display: grid;
    grid-template-columns: var(--col-1) var(--col-2) var(--col-3) var(--col-4);
    gap: 20px;
}

.grid__item:nth-child(1) {
    grid-column: span 2;
}

该示例第一个网格项目跨了两列(将第一列和第二列合并成一个网格区域),但这并不影响fr的计算。

或许上面的示例,大家对于fr设置弹性网格轨道理解起来还是有一定的困惑,不过我们可以将其和%结合起来,会更易于理解。简单地说:

1fr(即1fr)就是100%网格容器可用空间;2fr(即2fr)是各50%网格容器可用空间,即1fr50%网格容器可用空间。以此类似,要是你有25fr(即25fr),那么每个fr1fr)就是1/254%

使用饼图可以很形象的描述fr

注意,一个饼图(圆)就相当于网格容器的可用空间,分割的份数就相当于设置了弹性系数的网格轨道

无论你是要使用fr还是%来设置网格轨道的值,都可以按下面的方式来执行:

  • ①:决定有多少个网格轨道(列或行)
  • ②:进行计算
  • ③:创建轨道
  • ④:将数值应用于每个网格轨道

先使用%。假设网格容器宽度是800px(在无其他网格属性显式设置之下,这个值也是网格容器的可用空间),并且该网格有四列(为了好区别,给每个网格列轨道分别取个名,比如“Tom”、"Jack"、"Lucy"和"Nick")。 同时希望列宽相等,而且填充整个网格容器(网格容器可用空间全部用完),那么每列设置的值就是100 ÷ 4 × 100% = 25%

.grid__container {
    width: 800px;

    display: grid;
    grid-template-columns: 25% 25% 25% 25%;
}

每列网格轨道宽度都是网格容器可用空间(800px)的 25%,即:800px × 25% = 200px

但如果后来你决定让其中一列的宽度(比如“Tom”)是其他列的宽度的两倍,即 2x + 1x + 1x + 1x = 100,那么x是多少?如果仅仅是粗暴的将“Tom”列设置为50%,其他依旧是25%,这样就假设x = 25%了:

.grid__container {
    grid-template-columns: 50% 25% 25% 25%;
}

网格“Nick”列轨道溢出了网格容器:

前面介绍%时说过,网格轨道取值%是相对于网格容器宽度计算。

如果我们希望网格轨道不溢出网格容器,就需要重新计算 x的值,即 2x + 1x + 1x + 1x = 100,就可以算出 x = 100 ÷ 5 = 20,一个x就是网格容器可用宽度的 20%,对应的就是:160px

  • ①:“Tom”列宽就是 2x = 2 x 20% = 40%
  • ②:“Jack”列宽就是 1x = 1 x 20% = 20%
  • ③:“Lucy”列宽就是 1x = 1 x 20% = 20%
  • ④:“Nick”列宽就是 1x = 1 x 20% = 20%

算出每个x值之后,需要重新改变每一列的轨道值:

.grid__container {
    grid-template-columns: 40% 20% 20% 20%;
}

重新计算并设置网格列轨道值之后,网格列不会溢出网格容器了,但每列的列宽就变了,但还是保持了“Tom”列宽是其他列宽的两倍:

如果说需要再新增一列(比如“Tony”),这样一来网格容器就有五列,且每列网格轨道都是25%。即:

.grid__container {
    grid-template-columns: 25% 25% 25% 25% 25%;
}

新增的“Tony”列将会溢出网格容器:

如果不希望新增的列溢出网格容器,那么就需要五列加起来的总数是100,每列是100 ÷ 5 = 20%。需要调整每列的宽度:

.grid__container {
    grid-template-columns: 20% 20% 20% 20% 20%;
}

你会发现,网格列轨道的尺寸使用百分比值,在新增或删除一列,或添加网格沟槽,或改变它们的大小,都需要:

  • 重新计算每一列网格轨道的大小
  • 将新的尺寸值重新应用于每列网格轨道

fr将改变这一切。同样的,需要创建四列,并且每列的大小都是相等的,即每一列的宽度是1fr

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

把网格容器可用空间(800px)当作是一个饼,它被网格列轨道(“Tom”,“Jack”,“Lucy”和“Nick”)分成了四等份(即41fr),每等份(1fr)等于 1 ÷ 4 = .25(即25%):

%有点类似,如果你想“Tom”列是是其他列的两倍,只需要将这一列设置为2fr

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

同样的,网格可用空间这张饼图(800px)分成了四份,其中“Tom”列是2fr,等于2fr,而且是其他列(“Jack”,“Lucy”和“Nick”)的两倍(1fr)。所以1fr的值是1 ÷ 5 = 20%2fr + 1fr + 1fr + 1fr = 5fr)。因此,“Tom”列的2fr2/540%):

网格轨道使用fr单位时,如果饼图大小改变了(网格容器可用空间改变了),我们也不需要重新调整网格轨道的值:

同样的原理,即使有网格沟槽的出现(比如20px)或者新增非fr的网格轨道出现(比如“Tony”列列宽是200px):

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

如果用饼图来描述的话,“Tony”列相当先把饼图分去200px,其中网格沟槽占用了80px(即20px x 4),整个饼图并不完整了,只剩下 520px800px - 280px),相当于网格容器可用空间只有520px。不过,剩下的饼图同样分成了四分,“Tom”列是其他列的两倍,因为它是2fr2fr),其他列(“Jack”,“Lucy”和“Nick”)是1fr1fr),每个fr1/520%):

如此一来,1fr我们可以用两种方式来描述:

  • 用分数表示:1fr = 1 ÷ 所有网格轨道弹性系数总和(fr总和)
  • 用百分比表示:1fr = 100 ÷ 所有网格轨道弹性系数总和(fr总和)

用示例来描述的话:

  • ①:所有网格列轨道是4列,且每列宽都是1fr,那么 1fr = 1 ÷ 4 = 1/4 = 25%,即网格容器可用空间的25%
  • ②:设置网格容器可用空间是800px,那么每个fr计算出来的值是 800 x 1/4 = 200px800 x 25% = 200px
  • ③:如果网格容器新增一列,此时所有网格列轨道就是5,那么 1fr = 1 ÷ 5 = 1/5 = 20%,即网格容器可用空间的20%
  • ④:如果设置第一列(或某个列)的宽度是2fr,其他列宽仍然是1fr,那么fr总和是 2fr + 1fr + 1fr + 1fr + 1fr = 6fr
  • ⑤:此时,1fr = 1/6 = 16.6667%,即网格容器可用空间的 16.6667%;第一列是2fr,它的值是 2fr = 2/6 = 33.33%,即网格容器可用空间的33.33%
  • ⑥:如果设置第一列宽度是200px,其他列仍然是1fr宽。现在的总数是 200px + 1fr + 1fr + 1fr + 1fr = 200px + 4fr,那么所有列的fr总和就是4fr,对应的1fr = 1/4 = 25%,即网格容器可用空间的25%。由于第一列宽度是200px,那么网格容器可用空间就是 800px - 200px = 600px(即网格容器宽度减去第一列列宽)。也就是说,1fr等于网格容器可用空间600px25%,等于600 x 25% = 150px(即1fr=150px

在 CSS 网格布局中,1fr 的计算大致就是这样的一个过程。不过值得一提的是,设置 1fr 的网格轨道并不代表着网格轨道的列宽(或行高)都是相等的。这个就好比 Flexbox 布局中的 flex 属性,即所有设置 flex:1 的Flex项目并不一定就是相等的(或者说均分容器)。这是因为,它和内容有着关联。换句话说,只要内容是灵活的(网格项目大小会随着内容扩大或收缩),一个fr单位就是总量的一部分 。意思是说,只要网格项目中的内容能够缩放以适合该网格轨道(列或行),设置 1fr 网格轨道的大小就相等。然而,一旦网格项目内容停止缩放以适应网格轨道,设置fr值的网格轨道就会被重新调整,使内容能更好的适配。比如,如果网格布局中其中有一列具有一个固定宽度的网格项目,该网格列轨道的宽度将永远不会小于这个网格项目的宽度。

所以,一个具有1fr的网格列轨道,其最小值等于内容的宽度(也就是前面提到的min-content),无论这个宽度是一个固定单位值(比如400px),还是一个文本节点中最长的字。如果这种情况在网格布局中产生的话,那么其他设置1fr值的网格轨道就会相应的按比例缩小(其实在前面初步介绍fr时有提到这样的情景)。为了增强大家对其的理解,我们来看下面这个示例。

在这个示例中,总共有四例,且每一例的宽度都为1fr

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

你将看到的效果是 网格布局中所有网格列轨道都相等,这是因为 1fr = 1/4,即 1fr 等于网格容器可用空间的25%。在下一个网格布局中,将示例中“Tom”列中的文本换成一张图片(<img />),并且在代码中显式设置:

.grid__item > img {
    width: 400px;
    max-width: none;
    aspect-ratio: 16 / 9; /* 图片按照 16:9 比例等比缩放 */
}

按照 1fr 的计算方式,在该例中,1fr=200px,而事实上,插入图片的这列的宽度是不可能小于400px(它比计算出来的1fr的值要更大)。虽然如此,但并不代表着该列的1fr就等于400px,它仅能代表的是该列的列宽现在是400px(因为该列的最小内容宽度就是imgwidth值)。这就好比,该列先分去了网格容器可用空间(网格容器可用空间由 800px减少到 400px),其他三列各占剩余空间的1fr,即 1fr = 1/3=33.333%,计算出来其他三列的列宽现在是 400px x 33.333%,大约是133.33px

fr会自动设置最小值(min-width),这将尊重里面的内容,相当于 min-width的值为min-content。所以对于fr来说:

最小值(min-width)是自动设置的(相当于min-content);最大值(max-width)就是我们设置的值(比如1fr2fr3fr等)

所以,1fr在技术是min/max(auto,1fr),意味着 min=auto(即min-width: min-content),max=1fr。为此,我们可以在网格项目中显式设置min-width/heightmax-width/height来覆盖1fr计算出来的值;具有最小或最大值的网格轨道将大于或等于最小值,小于或等于最大值。

虽然在网格项目中可以显式设置min-*max-*指定网格轨道的最小和最大尺寸,但使用的时候需要额外注意,比如说,网格项目中设置的max-width小于该列网格轨道1fr计算出来的值时会造成网格项目内容溢出:

上面这个示例在第一个网格项目中设置了max-width,值auto180px之间切换,你可以看到max-width设置180px时,网格项目宽度小于该列轨道宽度(1fr计算出来的值),但当网格项目内容宽度大于max-width时,会溢出该网格项目,比如示例中的图片:

在网格项目上显式设置min-width值和设置max-width值类似,比如下面这个示例,尝试着切换min-width的值,可以看到第四列中的网格项目的变化:

花了较长的篇幅和大家一起探讨了 fr 的计算方式以及其中一些易被大家忽略不理解错误的细节,但这是值得的,因为在 CSS 网格布局中,fr 是一个非常有用的单位,也是 CSS 网格布局中独有的特性。虽然 fr 很强大,但也不能说随时随地者可以使用fr。比如说:

  • calc()中表达式中使用 fr 就无效,因为fr<flex>值类型,它和其他<length>值类型不兼容
  • gap属性中也不能使用 fr,因为 fr 是用来指定网格轨道尺寸的,不是用来指定网格沟槽大小的
  • calc()中使用var()fr计算也是一种无效行为,比如 .container{--fr: 2; grid-template-columns: calc(var(--fr) * 1fr)

fr 有点类似 flex,一般情况都将其视为默认动态单位,可以根据容器剩余空间来指定网格轨道。类似fr这样的动态单位,特别适用于基于比例布局中。比如两列布局,侧边栏固定宽度,主内容自动分割网格容器的可用空间:

.container {
    grid-template-columns: 220px 1fr;
    gap: 20px;
}

另外,在大多数情况下,用fr来替代%分更灵活,特别是有按比例分割网格轨道,网格轨道之间有固定大小的沟槽:

.container {
    grid-template-columns: repeat(4, 1fr);
    gap: 20px;
}

在 CSS 中可以用来设置尺寸大小的单位有很多,只不过这里只和大家探讨了几个常用于设置尺寸大小的单位,比如pxremvw等,但并不代表着其他设置尺寸大小的单位就不能用来指定网格轨道大小。接下来,和大家一起来探讨如何使用一些关键词来指定网格轨道大小,特别是min-contentmax-contentfit-content等。不过我们先从简单而又常见的noneauto开始。