CSS Grid中的auto-fill和auto-fit
Web布局相关的话题一直以来都是一个永恒的话题,不管发展到什么时候,都不会缺这样的话题。一直以来,我都非常推崇CSS Grid的布局模块,在我的认知中,CSS Grid布局是一个强大的布局,它将灵活的解决我们以往在Web中碰到的大多数问题。正因为CSS Grid布局很强大,因此它的体系也很庞大。比如@Juan Martín García就用CSS Grid向大家展示了如何通过一行CSS代码实现特定环境下的响应式布局效果。
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
在不依赖任何媒体查询的条件之下就可以实现上图所示的效果。
实现该效果运用到了CSS Grid中的几个特性:repeat()
、minmax()
、fr
和 auto-fit
(或 auto-fill
)。换句话说,构建上图这样的响应式布局效果,使用CSS Grid来实现的话离不开这几个属性,但在这篇文章中主要想和大家一起探讨 auto-fit
和 **auto-fill
**之间的差异。希望对大家有所帮助。
简单地回忆repeat()
和minmax()
auto-fit
和auto-fill
主要被运用于CSS Grid中的repeat()
函数中。
由于auto-fit
和auto-fill
和repeat()
函数紧密绑定在一起使用,因此,在介绍auto-fit
和auto-fill
之前,我们很有必要的了解一下CSS Grid中的repeat()
函数。
在Web布局中时常会碰到平铺的场景,在CSS Grid布局实现这样的布局效果非常的容易。那是因为CSS Grid中有一个强大的函数repeat()
。比如说,有一个卡片平铺的布局,卡片的宽度都是300px
,在CSS Grid中可以这样来处理:
.card__container {
display: grid;
grid-template-columns: 300px 300px 300px 300px;
}
上面的代码告诉Web浏览器,card__container
容器通过grid-teplate-columns
将容器分成了四列,每列的宽度都是300px
。对于这样的场景(每列的宽度相等),那么可以使用repeat()
函数,让布局变得更简单:
.card__container {
display: grid;
grid-template-columns: repeat(4, 300px)
}
我们用一张图来描述,会更易于理解:
repeat()
同样可以运用于grid-template-rows
属性中。
另外,repeat()
函数的第二个参数还可以是一个复合值,比如:
.card__container {
grid-template-columns: repeat(3, 40vmin 30vmin)
}
意思是40vmin 30vmin
被重复3
次,这样一来就构建了一个6
列的网格:
同样用图来解释:
事实上,在repeat()
函数的第二个参数中还可以结合minmax()
函数一起使用。其中minmax()
函数接受两个参数,即 minmax(minVal, maxVal)
。如果maxVal
小于minVal
,则minmax(minVal, maxVal)
会忽略maxVal
,将minmax(minVal, maxVal)
视为minVal
。
比如,我们把上面的代码稍作调整:
.card__container {
grid-template-columns: repeat(4, minmax(30vmin, 1fr))
}
效果如下:
你可以在上面的示例中尝试着拖动card__container
来改变其大小,可以发现每个.card
的宽度(列宽)会在30vmin
和1fr
之间变化:
如果你足够仔细的话,你可能已经发现了repeat()
函数中都显式指定了列数,但很多平铺效果的布局中,我们并不知道准确的列数,这个时候就需要用到auto-fill
或auto-fit
。
重复填充:auto-fill
和 auto-fit
auto-fill
和auto-fit
在CSS Grid布局中的作用是什么?
为了能比较形象的给大家描述清楚auto-fill
和auto-fit
是什么,我们通过一个示例来描述。假设在Web布局中有这样的一个卡片平铺的布局效果。当我们知道卡片容器.card__container
的大小、卡片数量、卡片宽度和卡片之间的间距时,我们可以很容易地在repeat()
函数中指定卡片的在容器的重复个数以及宽度,比如前面看到的示例:
.card__container {
grid-template-columns: repeat(4, 300px);
}
但问题是,当卡片容器无法有足够的空间容纳时,那么卡片就会溢出容器,打破布局:
.card__container {
display: grid;
grid-template-columns: repeat(3, 200px);
gap: 2vmin;
width: 500px;
}
效果如下:
就上面这个示例而言,容器的宽度显式设置为200px
,卡片宽度为200px
,卡片之间(列间距为2vmin
),这样一来,所有卡片的其之间间距的宽度总和是:
w' = width * 3 + gap * 2 = 200px * 3 + 2vmin * 2
相当于:
width: calc(200px * 2 + 2vmin * 2)
溢出的宽度即是:
w'' = width * 3 + gap * 2 - W = 200px * 3 + 2vmin * 2 - 500px
即:
面对这样的场景,作为开发者总是希望,卡片容器中的卡片数量能够根据卡片容器、卡片宽度以及它们之间的间距做出一个最佳匹配。那么在CSS Grid中就可以轻易的做到,即: 在repeat()
函数中使用auto-fill
或auto-fit
替代确切的重复列数:
.card__container {
grid-template-columns: repeat(auto-fill, 200px);
}
// 或
.card__container {
grid-template-columns: repeat(auto-fit, 200px);
}
auto-fill
和auto-fit
关键词会告诉浏览器处理列的大小和元素换行,以便当容器宽度不足以容纳元素时,元素会自动换行而不会造成溢出。但auto-fill
和auto-fit
两者之间还是有一些细微的差异。接下来就来探讨它们之间的差异以及幕后到底发生了什么?
auto-fill
VS. auto-fit
auto-fill
和auto-fit
之间的差异,先上张图来描述:
auto-fill
:在同一行中填充尽可能多的列。因此,只要能容纳新的列,就会自动创建隐式列,因为它试图在同一行中填充尽可能多的列。新添加的列(隐式列)可以是空的,也可能是空的,但是它们仍然会在行中占据指定的空间auto-fit
:将当前可用的列扩展到空间中,以便它们占用容器可用空间。当容器有可用空间时,浏览器会将可用空间均分给列,让列自动变宽填满整个容器;当容器可用空间为负值时,会另起一行排列
为了让大家减少文字描述带来的困惑,我们用@Ana Tudor在CodePen的一个示例来向大家阐述这种行为,能帮助大家更好的理解auto-fill
和auto-fit
之间的差异:
在这个示例中,使用了minmax()
来定义列的宽度,其中最小宽度是15%
,最大宽度是1fr
;而每行的列数分别使用了auto-fill
和auto-fit
:
.auto-fit {
grid-template-columns: repeat(auto-fit, minmax(15%, 1fr))
}
.auto-fill {
grid-template-columns: repeat(auto-fill, minmax(15%, 1fr))
}
拖动示例中滑块,改变容器中的列数,你可以看到auto-fit
和auto-fill
的差异:
这个示例,在minmax()
中使用的都是相对单位值,所以在改变容器大小时,列只会改变大小,列并不会在新的一行显示(容器没有足够空间):
为了能更好的展示auto-fill
和auto-fit
的作用以及差异,我们对上面的示例稍作修改,将示例中的15%
换成150px
:
.auto-fit {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr))
}
.auto-fill {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr))
}
效果如下:
尝试着改变容器大小,当容器没有足够空间时,卡片会另起一行排列:
你可能已经发现了,在一定程度上,auto-fill
和auto-fit
显示的结果是相同的:
虽然在浏览器上显示的结果相同,但它们的内在行为并不完全相同。在特定的视窗宽度下,它们会给出相同的结果。auto-fill
和auto-fit
开始显示不同结果的时间点主要取决于grid-template-columns
定义的列数和大小。因此在不同的示例中会有所不同。
就上面示例而言,视窗宽度和网格容器宽度相同
当视窗宽度足够大(网格容器宽度足够大),可以将一个(或多个)额外的列放入到同一行中时,auto-fill
和auto-fit
之间的差异就很明显。此时,浏览器针对这种情况会有两种处理方式,其处理方式很大程度上取决于是否有内容要放到额外的列中。
auto-fit
和auto-fill
如何工作
我们来看看auto-fit
和auto-fill
是如何工作的。同样用图来向大家展示auto-fit
和auto-fill
之间的区别。比如下图所示。我调整视窗的大小(改变网格容器大小),可以明显地看到auto-fit
和auto-fill
的差异:
如果还不够清楚,在Firefox浏览器打开网格编辑器,查看auto-fill
状态下网格线的变化:
你是否发现了,如果网格容器有足够多的空间时,auto-fill
会自动的创建隐式的列,而且列的宽度是minmax()
中的minVal
。最终这些创建的空列同样会将网格容器填满:
上图中的三个红色的框就是auto-fill
创建的隐式列。
但auto-fit
不同,当网格容器有足够的空间,auto-fit
并不会创建隐式的列(列数和初始化相同),不同的是,auto-fit
会让列取的值是minmax()
中的maxVal
,在该示例中是1fr
,因此会均分网格容器剩余的空间:
如果minmax()
的第二个参数maxVal
不是一个相对单位的值,是一个固定值,比如:
.auto-fit {
grid-template-columns: repeat( auto-fit, minmax(150px, 300px))
}
你发会现,当网格容器没有足够空间容纳列时,列会自动换行:
接着再回过头来看auto-fill
:
.auto-fill {
grid-template-columns: repeat( auto-fill, minmax(150px, 300px))
}
这个时候的表现和auto-fit
是一样的:
如果上面描述的不够清楚,可以尝试着调整下面Demo中的列数:
再来看另外一种情景,就是repeat()
函数中不使用minmax()
。同样拿@Ana Tudor 写的另一个案例来举例:
.auto-fit {
grid-template-columns: repeat(auto-fit, 14vw);
}
.auto-fill {
grid-template-columns: repeat(auto-fill, 14vw)
}
效果如下:
先来看网格容器有足够空间的情景,效果如下:
尝试着调整视窗大小,你会发现效果是这样的:
这里主要是由于示例中采用的是视窗单位vw
,当你改变视窗大小时,列的宽度也会随着变化:
如果换成固定单位时,auto-fill
或auto-fit
在容器没有足够空间时,会让列断行排列:
简单地小结一下
只有当容器有足够空间可容纳更多列时,auto-fill
和auto-fit
适应列大小的区别才会明显。
如果使用的是auto-fit
,且repeat()
结合minmax(val, 1fr)
同时使用,网格容器有剩余空间时,将会把剩余空间分配给列,从效果上看,列会拉伸到填满整个容器。而使用auto-fill
时,会创建隐式的列,就像用空列排满网格容器。
至于auto-fit
和auto-fill
具体使用哪个,应该根据实际的场景来选择。这样才会更有意义。
来看一个示例
我们来使用auto-fit
和minmax()
实现一个网格平铺的效果。就拿卡片类平铺来举例吧:
这个示例主要就是使用了前面介绍的内容,即repeat()
、auto-fit
和minmax()
。示例通过minmax(300px, 1fr)
设置了卡片的最小宽度是300px
,最大的宽度为1fr
,会根据容器可用空间合理计算出卡片的最大宽度。
示例中使用React构建了一个Card
组件:
const Card = (props) => {
const { imgUrl, imgDes, cardTitle, cardDes } = props;
return (
<li className="card">
<div className="card__object">
<img src={props.imgUrl} alt={props.imgDes} />
</div>
<div className="card__body">
<h4 className="card__title">{props.cardTitle}</h4>
<p>{props.cardDes}</p>
</div>
</li>
);
};
为了更易于展示Demo,这里构建了几个假数据,然后map()
出整个卡片列表:
const App = () => {
const CardItem = CardData.map((item, index) => (
<Card
imgUrl={item.imgUrl}
imgDes={item.imgDes}
cardTitle={item.cardTitle}
cardDes={item.cardDes}
key={index}
/>
));
return <ul className="card__container">{CardItem}</ul>;
};
就该示例而言,我们更关注的是CSS。换句话说,我们简单的几行代码就可以很容易的实现卡片平铺效果:
.card__container {
gap: 2vmin;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
这段代码很神奇,卡片能根据容器(此例就是视窗大小)自动匹配出合理的列数(即,每行显示多张卡片):
就这么几行简单地代码,就实现了卡片平铺效果,而且具备响应式效果。
特别声明:《你可能不太熟知的布局技巧》一文整理一些有关于布局方面的技巧,类似上例一样,通过简单几行代码就可以实现一个难以想象的布局效果。
CSS Grid布局是非常强大的,我们在上面的示例上,稍作一点处理,效果就会另一个样,比如说使用span
合并列:
.card:nth-child(1) {
grid-column: span 2;
}
.card:nth-child(6) {
grid-column: span 3;
}
除了使用span
之外,还可以像下面这样使用:
.card {
grid-column: (start track) / (end track);
}
虽然这样可以让我很轻易的对卡片进行合并(类似于表格中的合并单元格),但在一些特定容器宽度下,布局效果略有瑕疵:
不过不用担心,CSS Grid中提供了一个属性,可以帮助我们轻易的避免这种现象:
.card__container {
grid-auto-flow: dense;
}
最终的效果如下:
你可以尝试在新的标签页面打开这个Demo,拖动浏览器改变视窗大小查看Demo效果。
小结
现在,你可以通过CSS Grid模块提供的功能,使用最少的代码创建灵活的响应式网格(即响应式布局),并且这个效果不需要依赖任何媒体查询特性。
换句话说,当你使用类似repeat(auto-fit,minmax(300px, 1fr))
即可实现带有响应多功能网格平铺效果,而且结合其他特性,能实现更强大的布局效果。这也是业内很多人所说的,一行代码实现响应式布局的效果。当然,在这篇文章中,还花了一定的篇幅和大家探讨了auto-fit
和auto-fill
之间的差异,以及工作原理。
我想,你要是能坚持阅读到这里,你对auto-fit
和auto-fill
会有一定的理解,也能清楚的知道在实际运用中应该选择使用哪个。如果你在这方面有更多的建议或经验,欢迎在下面的评论中与我们共享。