图解CSS: Grid布局(Part5)
前面花了两个章节(Grid布局 Part3 和 Part4)介绍了在grid-template-columns
和 grid-template-rows
中使用固定长度值,动态值和一些关键词来设置网格轨道的大小。除了这些之外,还可以使用一些函数,比如minmax(min, max)
、repeat()
等来设置网格轨道大小。在这一章节点,主要围绕着在 grid-template-columns
和 grid-template-rows
属性中如何使用函数(minmax(min, max)
和 repeat()
)设置网格轨道尺寸,感兴趣的话,请继续往下阅读。
minmax(min, max)
minmax(min, max)
是一个 CSS 函数,可以用在 grid-template-columns
和 grid-template-rows
属性上,用来设置网格轨道的尺寸。我们可以给 minmax()
函数传两个参数值,即 一个是 min
和 max
。每个参数都可以是 <length>
(比如px
)、<percentage>
(比如%
)、<flex>
(比如fr
)的一种,或者是max-content
、min-content
、auto
等关键词。
CSS 规范对 minmax(min, max)
是这样定义的:
定义了一个大于或等于
min
和小于或等于max
的尺寸范围。
简单地说,minmax(min, max)
函数将返回一个 min ~ max
范围中的一个值。该函数返回的值可以作为grid-template-columns
或 grid-template-rows
的值。我们来看一个简单地示例:
.container {
grid-template-columns: minmax(100px, 200px) 1fr 1fr;
}
我们简单分析上面这个示例。示例中使用 grid-template-columns
创建了一个三列网格,其中:
- 第一列的列宽是
minmax(100px, 200px)
。min
是100px
,max
是200px
- 第二列和第三列的列宽都是
1fr
。意味着,它们将占网格容器有用空间的1/2
,即,容器宽度的50%
。
我们来考虑一下不同宽度的网格容器,可以拖动示例中的滑块来调整网格容器的宽度:
- 当网格容器宽度足够宽时,
minmax(100px, 200px)
返回的值是200px
,即第一列的网格轨道宽度是200px
(max
的值) - 当网格容器宽度调到很小时(比如
200px
),minmax(100px, 200px)
返回的值是100px
,即第一列的网格轨道宽度是100px
(min
的值) - 另外,
minmax(100px, 200px)
还会返回一个100px ~ 200px
之间的值,比如当容网格容器宽度是290px
时,minmax(100px, 200px)
返回的值就是143px
在这个示例中,minmax(min, max)
函数中的 min
和 max
都是静态的长度值(<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)
函数中的两个参数 min
和 max
还可接受一些动态值(需要计算的值),比如我们熟悉的%
和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%)
中的min
和max
在根据网格容器宽度计算出来的min
值总是小于max
值,直到网格容器宽度为0
时,min
和max
的值才相等。在这种情况之下,网格轨道尺寸还是根据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>
时,不建议min
和max
都取<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)
中max
取fr
值,可以和设置其他单位(除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)
函数中min
和 max
传 %
值时,演示了 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-content
、max-content
和 auto
等。如果网格列轨道使用这些关键词将网格轨道的尺寸与它所包含的内容关连起来。
.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-content
和max-content
时,可以得到一个响应性强,内容不会溢出,不会比其最宽的元素更宽等特性的网格。
在minmax(min, max)
函数中使用关键词 auto
时:
- 当
auto
作为max
值使用时(minmax(100px, auto)
),其值与max-content
(minmax(100px, max-content)
)等同 - 当
auto
作为min
值使用时,它的值由min-width
(或min-height
)指定,这意味着,auto
有时与min-content
等同,但并非总是如此
不过 fit-content
和 fit-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
表示最大值。如果VAL
在MIN
和MAX
之间,则使用VAL
作为函数的返回值;如果VAL
大于MAX
,则使用MAX
作为函数的返回值;如果VAL
小于MIN
,则使用MIN
作为函数的返回值
有关于 CSS 的比较函数
min()
、max()
和clamp()
更详细的介绍,可以阅读《聊聊min()
,max()
和clamp()
函数》一文。
虽然 min()
、max()
和 clamp()
函数非常优秀,但直接用于grid-template-columns
或grid-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-columns
和grid-template-rows
(设置网格轨道尺寸的属性)无效,但他们结合 minmax(min, max)
函数中却是有效的。@TerribleMia 在 Twitter 上也分享了这个话题:
.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-columns
和 grid-template-rows
属性,用来设置网格轨道尺寸大小。
repeat()
语法的通用形式是:
repeat( [ <integer [1,∞]> | auto-fill | auto-fit ] , <track-list> )
第一个参数用来指定重复的次数,第二个参数是一个轨道列表,它被重复该次数。然而,有一些限制:
repeat()
函数不能被嵌套- 自动重复(
auto-fill
或auto-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-fill
和 auto-fit
:
auto-fill
:如果网格容器在相关轴上具有确定的大小或最大大小,则重复次数是最大可能的正整数,不会导致网格溢出其网格容器。如果定义了,将每个轨道视为其最大轨道尺寸大小函数 (grid-template-rows
或grid-template-columns
用于定义的每个独立值。 否则,作为最小轨道尺寸函数,将网格间隙加入计算. 如果重复次数过多,那么重复值是1
。否则,如果网格容器在相关轴上具有确定的最小尺寸,重复次数是满足该最低要求的可能的最小正整数。 否则,指定的轨道列表仅重复一次。auto-fit
: 行为与auto-fill
相同,除了放置网格项目后,所有空的重复轨道都将折叠。空轨道是指没有流入网格或跨越网格的网格项目。(如果所有轨道都为空,则可能导致所有轨道被折叠。)折叠的轨道被视为具有单个固定轨道大小函数为0px
,两侧的槽都折叠了。为了找到自动重复的轨道数,用户代理将轨道大小限制为用户代理指定的值(例如1px
),以避免被零除。
简单地说,auto-fit
将扩展网格项目以填补可用空间,而auto-fill
不会扩展网格项目。相反,auto-fill
将保留可用的空间,而不改变网格项目的宽度。比如下图,可以看出 auto-fit
和 auto-fill
的差异:
@Ana Tudor 在 Codepen上提供了一个具体的示例:
你可以尝试着拖动示例中的滑块来改变网格项目数量,就可以看到 auto-fill
和 auto-fit
的差异:
你要是将 repeat()
函数和minmax(min,max)
,1fr
和 auto-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-fill
和auto-fit
关键词更详细的介绍,可以阅读《CSS Grid 中的auto-fill
和auto-fit
》一文。
待续 ...
到这里,有关于怎么设置网格轨道尺寸大小就先告一段落了。我们可以在 grid-template-colums
和 grid-template-rows
(以及它们的简写属性grid-template
)中使用不同的单位值,关键词和一些函数来设置网格轨道大小。这些方法用来设置网格轨道尺寸大小的时候,都会涉及一些简单的算法,不过有关于这些算法将会在后面的章节中单独拿出来介绍。
通过前面的学习,我们知道,grid-template-columns
和 grid-template-rows
是定义显式网格的方式,事实上,在 CSS 网格布局中,除了显式网格之外,还有隐式网格一说。那么接下来将和大家一起聊聊隐式网格相在的知识。