图解CSS: Grid布局(Part5)

发布于 大漠

前面花了两个章节(Grid布局 Part3Part4)介绍了在grid-template-columnsgrid-template-rows 中使用固定长度值,动态值和一些关键词来设置网格轨道的大小。除了这些之外,还可以使用一些函数,比如minmax(min, max)repeat() 等来设置网格轨道大小。在这一章节点,主要围绕着在 grid-template-columnsgrid-template-rows 属性中如何使用函数(minmax(min, max)repeat())设置网格轨道尺寸,感兴趣的话,请继续往下阅读。

minmax(min, max)

minmax(min, max) 是一个 CSS 函数,可以用在 grid-template-columnsgrid-template-rows 属性上,用来设置网格轨道的尺寸。我们可以给 minmax() 函数传两个参数值,即 一个是 minmax 。每个参数都可以是 <length>(比如px)、<percentage>(比如%)、<flex>(比如fr)的一种,或者是max-contentmin-contentauto等关键词。

CSS 规范对 minmax(min, max) 是这样定义的:

定义了一个大于或等于min和小于或等于max的尺寸范围

简单地说,minmax(min, max)函数将返回一个 min ~ max 范围中的一个值。该函数返回的值可以作为grid-template-columnsgrid-template-rows的值。我们来看一个简单地示例:

.container {
    grid-template-columns: minmax(100px, 200px) 1fr 1fr;
}

我们简单分析上面这个示例。示例中使用 grid-template-columns 创建了一个三列网格,其中:

  • 第一列的列宽是 minmax(100px, 200px)min100pxmax200px
  • 第二列和第三列的列宽都是 1fr。意味着,它们将占网格容器有用空间的1/2,即,容器宽度的 50%

我们来考虑一下不同宽度的网格容器,可以拖动示例中的滑块来调整网格容器的宽度:

  • 当网格容器宽度足够宽时,minmax(100px, 200px) 返回的值是 200px,即第一列的网格轨道宽度是 200pxmax的值)
  • 当网格容器宽度调到很小时(比如 200px),minmax(100px, 200px) 返回的值是 100px,即第一列的网格轨道宽度是 100pxmin的值)
  • 另外,minmax(100px, 200px) 还会返回一个 100px ~ 200px 之间的值,比如当容网格容器宽度是 290px时,minmax(100px, 200px) 返回的值就是 143px

在这个示例中,minmax(min, max) 函数中的 minmax 都是静态的长度值(<length>),并且只在一个网格轨道中使用 minmax(min, max)函数。其实,我们还可以使用 minmax(min, max) 在一个以上的网格轨道中使用。比如,我们把上面示例中的两个 1fr 都换成 minmax(100px, 200px),当然你也可以换成任何你希望使用的值:

.container {
    grid-template-columns: minmax(100px, 200px) minmax(100px, 200px) minmax(100px, 200px)
}

这个示例,三列网格轨道宽度都会在 100px ~ 200px 之间变动,最大是200px,最小是100px

除了静态值,minmax(min, max) 函数中的两个参数 minmax 还可接受一些动态值(需要计算的值),比如我们熟悉的%fr。通过使用它们,我们可以定义既具响应式(Responsiv),又能根据网格容器可用空间改变网格轨道尺寸。比如下面这个示例:

.container {
    grid-template-columns: minmax(30%, 50%) 1fr 1fr;
}

这个示例定义的网格,其中第一列网格轨道的宽度在 30% ~ 50%之间,而第二列和第三列使用的是1fr,即网格容器可用空间宽度的1/2(即网格容器可用空间宽度的50%)。你可以尝试着拖动示例中的滑块,当网格容器宽度改变之后,第一列网格轨道大小也会随之改,在这个示例中,有可能小到0,这是因为网格容器宽度是相对于其父容器按百分比计算的:

你可能已经发现了,在minmax(min, max)函数两个参数都使用<percentage>的时候,会令我们感到困惑,我们来做一个假设:

网格容器宽度 min = 30% max = 50% minmax(30%, 50%)计算结果
800px 30% x 800 = 240px 50% x 800 = 400px minmax(240px, 400px)
600px 30% x 600 = 180px 50% x 600 = 300px minmax(180px, 300px)
400px 30% x 400 = 120px 50% x 400 = 200px minmax(120px, 200px)
200px 30% x 200 = 60px 50% x 200 = 100px minmax(60px, 100px)
100px 30% x 100 = 30px 50% x 100 = 50px minmax(30px, 50px)
0px 30% x 0 = 0px 50% x 0 = 0px minmax(0px, 0px)

从上表可以知道 minmax(30%, 50%) 中的minmax 在根据网格容器宽度计算出来的min值总是小于max值,直到网格容器宽度为0时,minmax的值才相等。在这种情况之下,网格轨道尺寸还是根据minmax(min, max)返回值定义。假设在设置minmax(30%, 50%)列轨道中内容宽度为0(即该列中单元格宽度都为0),这个时候,该网格列轨道的宽度始终是max的值:

调整选择框中的值来改变网格容器的宽度,你将看到下面这样的效果:

接着继续往下,在上面的示例基础上,在网格项目中添加一张 150px x 150px 图像,也就是网格轨道中的网格项目内容宽度是 150px

网格容器宽度 网格项目内容宽度 min = 30% max = 50% minmax(30%, 50%)计算结果
800px 150px 30% x 800 = 240px 50% x 800 = 400px minmax(240px, 400px)
600px 150px 30% x 600 = 180px 50% x 600 = 300px minmax(180px, 300px)
400px 150px 30% x 400 = 120px 50% x 400 = 200px minmax(120px, 200px)
200px 150px 30% x 200 = 60px 50% x 200 = 100px minmax(60px, 100px)
100px 150px 30% x 100 = 30px 50% x 100 = 50px minmax(30px, 50px)
0px 150px 30% x 0 = 0px 50% x 0 = 0px minmax(0px, 0px)

最终网格轨道的宽度minmax(30%, 50%)返回的还是max的值:

换句话说,在minmax(min, max)中使用<percentage>时,不建议minmax都取<percentage>值,即使都是该百分类型的值,最终返回的是max值。也因此,更建议在minmax(min, max)中把%值和其他类型值结合起来使用,比如:

/* <inflexible-breadth>, <track-breadth> 值 */
minmax(400px, 50%) // ~> min = 400px; max = 50%
minmax(30%, 300px) // ~> min = 30%;   max = 300px

/* <fixed-breadth>, <track-breadth> 值 */
minmax(1fr, 50%)         // ~> min = 1fr;   max = 50%
minmax(400px, 50%)       // ~> min = 400px; max = 50%
minmax(30%, 300px)       // ~> min = 30%;   max = 300px
minmax(50%, min-content) // ~> min = 50%;   max = min-content

/* <inflexible-breadth>, <fixed-breadth> 值 */
minmax(400px, 50%)       // ~> min = 400px;        max = 50%
minmax(30%, 300px)       // ~> min = 30%;          max = 300px
minmax(min-content, 50%) // ~> min = min-content   max = 50%

只不过,这样结合在一起使用的时候,minmax(min, max)中的max有可能会比min值小,比如:

.container {
    grid-template-columns: minmax(300px, 50%);
}

当网格容器宽度小于600px时,max返回的值就会比min小,例如网格容器宽度是400px时,max = 50% x 400 = 200px,即 max = 200px,这个时候max将会忽略,那么minmax(min, max)返回的值是min,即300px

调整示例中百分比,你能看到效果如下:

这个观点是通用的:

minmax(min, max)函数,如果 max 小于 min 时,max 将会被忽略,最终 minmax(min, max) 将会取 min 的值。

换句话说,当我们在 minmax(min, max) 函数中使用动态值时(会相对其他属性进行计算,比如上面示例中所说的 %,和接下来示例中要用到的fr),设置一个有意义的规则至关重要。比如下面这个示例,对定义网格轨道没有任何意义:

.container {
    grid-template-columns: minmax(1fr, 2fr) 1fr 1fr;
}

示例中的 minmax(1fr, 2fr) 就是没有意义的一种设置,因为浏览器无法决定 minmax(min, max) 函数应该使用哪个值:

  • 如果 minmax(1fr, 2fr) 使用 min 值,则会是 1fr 1fr 1fr,按照前面介绍的 fr,那么 1fr = 1 / 3
  • 如果 minmax(1fr, 2fr) 使用 max 值,则会是 2fr 1fr 1fr, 按照前面介绍的 fr,那么 1fr = 1 / 4

而且使用开发者工具查看代码的时候,你会发现 minmax(1fr, 2fr) 用于设置网格轨道尺寸的属性中时,将是无效的:

针对这一点,W3C 规范中有做过相应的描述:

也就是说,如果你想在 minmax(min, max) 函数中使用 fr 单位时,只能用于max值中。换句话说,minmax(min, max)maxfr值,可以和设置其他单位(除fr之外)值的min混合使用,比如minmax(200px, 1fr)。即使是这样,也有可能 1fr 计算出来的值会小于200px,不过要是出现这种现象,minmax(200px, 1fr)并不会无效,只不过最终会取min的值200px

.container {
    grid-template-columns: minmax(200px, 1fr) 1fr 1fr;
}

拖动示例中滑块,当网格容器宽度变成 550px 时,minmax(200px, 1fr) 将会取min的值200px

前面在和大家演示 minmax(min, max) 函数中minmax% 值时,演示了 0% 的一个场景,但并没有详细的说 minmax(min, max) 中的 min 值为0 时会发生什么?比如:

.container {
    grid-template-columns: minmax(0, 300px) 1fr 1fr;
}

你猜对了!设置minmax(0, 300px) 的网格轨道的宽度是在 0 ~ 300px 之间变化,最小为 0,最大不会超过 300px

minmax(min, max) 函数中除了使用固定的<length>值,动态值,还可以使用一些关键词,比如前面介绍的 min-contentmax-contentauto等。如果网格列轨道使用这些关键词将网格轨道的尺寸与它所包含的内容关连起来。

.container {
    grid-template-columns: minmax(min-content, max-content) minmax(auto, 1fr) 1fr;
}

拖动示例滑块,可以看到相关变化:

minmax(min, max)中使用相关关键词时,minmax(min-content, min-content) 等同于 min-content,即:

.container {
    grid-template-columns: min-content;
}

// 等同
.container {
    grid-template-columns: minmax(min-content, min-content)
}

minmax(min, max)函数中同时使用min-contentmax-content时,可以得到一个响应性强,内容不会溢出,不会比其最宽的元素更宽等特性的网格。

minmax(min, max)函数中使用关键词 auto 时:

  • auto 作为max值使用时(minmax(100px, auto)),其值与max-contentminmax(100px, max-content))等同
  • auto 作为min值使用时,它的值由 min-width(或min-height)指定,这意味着,auto有时与min-content等同,但并非总是如此

不过 fit-contentfit-content() 不能用于minmax(min, max)函数中。

我们来看一个minmax(min, max)用例,使用它来创建一个布局,这个布局就是让内容水平居中:

.container {
    display: grid;
    grid-template-columns: minmax(1rem, 1fr) minmax(auto, 70ch) minmax(1rem, 1fr);
}

第一列和最后一列充当了网格沟槽,如果屏幕宽度没有足够空间,minmax(1rem, 1fr) 会取1rem;如果有足够空间,那么会取 1fr,足剩余空间宽度的 50%

第二列(设置minmax(auto, 70ch))才是重点,这意味着,这一列的最大宽度是每行 70 个字符。这是一个理想的字符长度,易于提高内容的可读性:

min()、max() 和 clamp()

min()max()clamp()CSS 函数中的一部分,被称为 比较函数 。它们和minmax(min, max)完全不同:

  • minmax(min, max) 返回的是 min~max 之间的一个值,最小是min,最大是 max
  • min() 返回的是函数列表参数中最小的值,比如 min(100px, 200px)返回的是 100px
  • max() 返回的是函数列表参数中最大的值,比如 max(100px, 200px)返回的是 200px
  • clamp(MIN, VAL, MAX) 更类似于minmax(min, max),返回的是一个区间值。即 clamp(MIN, VAL, MAX),其中MIN表示最小值,VAL表示首选值,MAX表示最大值。如果VALMINMAX之间,则使用VAL作为函数的返回值;如果VAL大于MAX,则使用MAX作为函数的返回值;如果VAL小于MIN,则使用MIN作为函数的返回值

有关于 CSS 的比较函数 min()max()clamp() 更详细的介绍,可以阅读《聊聊min()max()clamp()函数》一文。

虽然 min()max()clamp() 函数非常优秀,但直接用于grid-template-columnsgrid-template-rows属性中将是无效的:

.container {
    grid-template-columns: min(80px, 1fr) clamp(300px, 20vw, 1fr) max(auto, 80px)
}

不过他们可以用于 gap 属性上:

.container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    grid-gap: clamp(1rem, 2vw, 24px);
}

虽然 min()max()clamp() 函数直接使用grid-template-columnsgrid-template-rows(设置网格轨道尺寸的属性)无效,但他们结合 minmax(min, max) 函数中却是有效的。@TerribleMiaTwitter 上也分享了这个话题

.container {
    grid-template-columns: auto minmax(min-content, max(60ch, 50vw)) 1fr;
    grid-gap: clamp(1rem, 2vw, 24px);
}

repeat()

前面有多个示例,在grid-template-columns 中设置值时有相同的值,比如:

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

在 CSS 网格布局中提供了一个叫 repeat() 的函数,可以帮我们把上面的代码变得更简洁:

.container {
    grid-template-columns: repeat(3, 1fr)
}

repeat() 函数表示网格轨道列表的重复片段,允许以更紧凑的形式写入大量显示重复模式的网格轨道(列或行)。该函数可以用于 grid-template-columnsgrid-template-rows 属性,用来设置网格轨道尺寸大小。

repeat() 语法的通用形式是:

repeat( [ <integer [1,∞]> | auto-fill | auto-fit ] , <track-list> )

第一个参数用来指定重复的次数,第二个参数是一个轨道列表,它被重复该次数。然而,有一些限制:

  • repeat() 函数不能被嵌套
  • 自动重复(auto-fillauto-fit)不能与内在的或或灵活的尺寸相结合

因此,repeat() 函数精确的语法有下面几种形式:

<track-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <track-size> ]+ <line-names>? )
<auto-repeat>  = repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
<fixed-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
  • <track-repeat> :可以表示任何<track-size>的重复,但被限制在一个固定的重复次数
  • <auto-repeat> :可以自动重复以填补空间,但需要明确的轨道尺寸,以便计算重复次数。它只能在轨道列表中出现一次,但同一个轨道列表可以包含<fixed-repeat>

先来看几个 repeat() 函数常用的示例,比如:

.container {
    grid-template-columns: 1fr 2fr 1fr 2fr 1fr 2fr;
    grid-template-rows: 100px auto 20% 100px auto 20%;
}

上面的代码可以用repeat()来描述:

.container {
    grid-template-columns: repeat(3, 1fr 2fr);
    grid-template-rows: repeat(2, 100px auto 20%);
}

在网格布局中,还可以在 repeat() 函数中指定重复的网格线:

.container {
    grid-template-columns: repeat(4, [col] 1fr);
    grid-template-rows: repeat(5, [row] 150px);
}

如果不用repeat()函数的话,需要像下面这样来使用:

.container {
    grid-template-columns: [col] 1fr [col] 1fr [col] 1fr [col] 1fr;
    grid-template-rows: [row] 150px [row] 150px [row] 150px [row] 150px [row] 150px;
}

要是在 repeat() 函数中重复网格线名称的话,结束的网格线名称最终会与下一个开始网格线名称共享一个网格线名称:

.container {
    grid-template-columns: repeat(4, [col-start] 1fr [col-end]);
    grid-template-rows: repeat(5, [row-start] 150px [row-end]);
}

上面的代码等同于:

.container {
    grid-template-columns:
    [col-start] 1fr
    [col-end col-start] 1fr
    [col-end col-start] 1fr
    [col-end col-start] 1fr [col-end];

grid-template-rows:
    [row-start] 150px
    [row-end row-start] 150px
    [row-end row-start] 150px
    [row-end row-start] 150px
    [row-end row-start] 150px [row-end];
}

repeat() 函数中,还可以和其他的描述网格轨道的关键词或minmax(min, max)结合在一起使用:

.container {
    grid-template-columns: repeat(3, [col-start] min-content [col-middle] max-content [col-end]);
    grid-template-rows: repeat(3, minmax(100px, 1fr));
}

上面的代码等同于:

.container {
    grid-template-columns: 
        [col-start] min-content [col-middle] max-content [col-end] 
        [col-start] min-content [col-middle] max-content [col-end] 
        [col-start] min-content [col-middle] max-content [col-end];
    grid-template-rows: minmax(100px, 1fr) minmax(100px, 1fr) minmax(100px, 1fr);
}

把网格线显示出来的结果如下图所示:

前面展示的示例,在repeat()函数中的第一个参数都是具体的数值,其实除了使用具体的数值,还可以使用关键词auto-fillauto-fit

  • auto-fill :如果网格容器在相关轴上具有确定的大小或最大大小,则重复次数是最大可能的正整数,不会导致网格溢出其网格容器。如果定义了,将每个轨道视为其最大轨道尺寸大小函数 ( grid-template-rowsgrid-template-columns 用于定义的每个独立值。 否则,作为最小轨道尺寸函数,将网格间隙加入计算. 如果重复次数过多,那么重复值是 1 。否则,如果网格容器在相关轴上具有确定的最小尺寸,重复次数是满足该最低要求的可能的最小正整数。 否则,指定的轨道列表仅重复一次。
  • auto-fit : 行为与 auto-fill 相同,除了放置网格项目后,所有空的重复轨道都将折叠。空轨道是指没有流入网格或跨越网格的网格项目。(如果所有轨道都为空,则可能导致所有轨道被折叠。)折叠的轨道被视为具有单个固定轨道大小函数为 0px,两侧的槽都折叠了。为了找到自动重复的轨道数,用户代理将轨道大小限制为用户代理指定的值(例如 1px),以避免被零除。

简单地说,auto-fit 将扩展网格项目以填补可用空间,而auto-fill不会扩展网格项目。相反,auto-fill将保留可用的空间,而不改变网格项目的宽度。比如下图,可以看出 auto-fitauto-fill 的差异:

@Ana Tudor 在 Codepen上提供了一个具体的示例

你可以尝试着拖动示例中的滑块来改变网格项目数量,就可以看到 auto-fillauto-fit 的差异:

你要是将 repeat() 函数和minmax(min,max)1frauto-fill(或auto-fit)结合起来,可以很容易帮我们实现像下图这样的响应式布局效果:

实现上图这样的效果,代码很简单:

.container {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 20px;
}

改变视窗大小,效果如下:

不过,上面示例还有一个小小的缺陷,当屏幕宽度小于 250px 时,浏览器水平方向会出现滚动条:

可以使用媒体查询来优秀上面的示例:

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

@media (min-width: 300px) {
    .container {
        grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    }
}

如果不想使用媒体查询,可以换 CSS的比较函数来优化:

.container {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));
    gap: 20px;
}

有关于 repeat() 函数中 auto-fillauto-fit 关键词更详细的介绍,可以阅读《CSS Grid 中的 auto-fillauto-fit》一文。

待续 ...

到这里,有关于怎么设置网格轨道尺寸大小就先告一段落了。我们可以在 grid-template-columsgrid-template-rows (以及它们的简写属性grid-template)中使用不同的单位值,关键词和一些函数来设置网格轨道大小。这些方法用来设置网格轨道尺寸大小的时候,都会涉及一些简单的算法,不过有关于这些算法将会在后面的章节中单独拿出来介绍。

通过前面的学习,我们知道,grid-template-columnsgrid-template-rows 是定义显式网格的方式,事实上,在 CSS 网格布局中,除了显式网格之外,还有隐式网格一说。那么接下来将和大家一起聊聊隐式网格相在的知识。