大约十年前,我尝试修改自己创建的博客的外观时,发现了 CSS。很快,我就能用更数学化的、因此更容易理解的功能(如转换)来编写 酷炫的东西。但是,CSS 的其他领域(如布局)一直是持续的痛苦来源。
这篇文章讨论的是我在大约十年前遇到的一个问题,直到最近,我一直不知道如何以一种智能的方式解决它。具体来说,它讲述了我是如何使用现代 CSS 网格技术找到一个长期问题的解决方案的,在这个过程中,我得到了比我最初想象的还要酷炫的结果。
请注意,这不是关于如何最佳使用 CSS 网格的教程,而更像是我自己的学习过程的回顾。
问题
我最开始在那个博客上发布的东西之一是来自城市的随机照片,所以我想出了一个想法,要创建一个固定大小的缩略图网格。为了获得更好的外观,我希望这个网格相对于其上下的段落居中对齐,但同时,我希望最后一行上的缩略图相对于网格左对齐。同时,文章的宽度(以及其中网格的宽度)将取决于视窗。
HTML 代码如下所示
<section class='post__content'>
<p><!-- some text --></p>
<div class='grid--thumbs'>
<a href='full-size-image.jpg'>
<img src='thumb-image.jpg' alt='image description'/>
</a>
<!-- more such thumbnails -->
</div>
<p><!-- some more text --></p>
</section>
它看起来很简单,但事实证明,这是我遇到的最困难的 CSS 问题之一。
不太理想的解决方案
这些是我尝试过的或多年来看到别人建议过的东西,但从未真正奏效。
浮动不可能
浮动结果是一条死胡同,因为我不知道如何用这种方式使网格居中对齐。
.grid--thumbs { overflow: hidden; }
.grid--thumbs a { float: left; }
下面的演示展示了浮动尝试。调整嵌入大小以查看它们在不同视窗宽度下的行为。
inline-block
疯狂
一开始,这似乎是一个更好的主意
.grid--thumbs { text-align: center }
.grid--thumbs a { display: inline-block }
但事实并非如此
在这种情况下,最后一行没有左对齐。
在某个时候,由于 CodePen 上的意外 CSS 自动完成功能,我发现了名为 text-align-last
的属性,它决定了块的最后一行如何对齐。
不幸的是,在网格上设置 text-align-last: left
也不是我想要的解决方案
在这一点上,我实际上考虑放弃居中对齐网格的想法。text-align: justified
和 text-align-last: left
的组合在网格上会产生更好的效果吗?
嗯,事实并非如此。也就是说,除非最后一行只有一张缩略图,并且列之间的间隙不太大。调整下面的嵌入大小以了解我的意思。
这基本上是我两年前所处的位置,经过九年的尝试和失败,我仍然没有找到这个问题的解决方案。
凌乱的 flexbox 技巧
一个一开始看起来可行的 flexbox 解决方案是在网格上添加一个 ::after
伪元素,并在缩略图和这个伪元素上设置 flex: 1
.grid--thumbs {
display: flex;
flex-wrap: wrap;
a, &::after { flex: 1; }
img { margin: auto; }
&:after { content: 'AFTER'; }
}
下面的演示展示了这种方法的工作原理。我已经给缩略图和 ::after
伪元素添加了紫色轮廓,以便更容易看到正在发生的事情。
这不太是我想要的,因为缩略图网格没有居中对齐。也就是说,它看起来并不太糟糕……只要最后一行比其他行少一个图像。但是,一旦发生改变,如果缺少更多项目或没有项目,布局就会失效。

::after
技巧不可靠。这是一个非常规的想法。另一个不是使用伪元素,而是根据我们要期望具有的列数,在缩略图之后添加相同数量的空 div。
我们可以估计出期望的列数,因为缩略图的大小是固定的,并且我们可能希望设置文章的最大宽度,因为跨越整个屏幕宽度的文本在视觉上难以阅读。在这种情况下,用缩略图的固定宽度除以最大宽度应该会得到最大列数。
第一个空元素将占据未完全填充缩略图的行上的全部宽度,而其余元素将溢出到其他行。但由于它们的 height
为零,因此在视觉上无关紧要。
这有点像完成了任务,但同样,它非常规,并且仍然没有产生我想要的确切结果,因为它有时会在列之间留下很大且看起来很丑陋的间隙。
网格解决方案?
鉴于它的名称,网格布局一直听起来像答案。问题是,到目前为止,我看到的所有示例都使用了预定义的列数,而这对于这种特定模式不起作用,因为列数由视窗宽度决定。
去年,在编写一系列包含一个元素的纯 CSS 背景模式时,我想到了生成大量媒体查询来修改 CSS 变量 --n
的想法,该变量对应于用于设置 grid-template-columns
的列数。
$w: 13em;
$h: 19em;
$f: $h/$w;
$n: 7;
$g: 1em;
--h: #{$f*$w};
display: grid;
grid-template-columns: repeat(var(--n, #{$n}), var(--w, #{$w}));
grid-gap: $g;
place-content: center;
@for $i from 1 to $n {
@media (max-width: ($n - $i + 1)*$w + ($n - $i + 2)*$g) {
--n: #{$n - $i}
}
}
实际上,当时我对这个想法非常自豪,即使现在回过头来看,我仍然感到有点尴尬。对每个可能的列数都创建一个媒体查询并不理想,更不用说它在网格宽度不等于视窗宽度时效果不太好,但它仍然有点灵活,并且还取决于其同级元素的宽度。
神奇的解决方案
在使用 CSS 网格并无法理解为什么 repeat()
函数在特定情况下不起作用时,我终于找到了一种更好的解决方案。这非常令人沮丧,促使我去 MDN 查找,在那里我偶然发现了 auto-fit
关键字,虽然我不理解解释,但我有一种预感,它可以帮助解决这个其他问题,因此我放弃了正在做的一切,试了一下。
以下是我的结果
.grid--thumbs {
display: grid;
justify-content: center;
grid-gap: .25em;
grid-template-columns: repeat(auto-fit, 8em);
}
我还发现了 minmax()
函数,它可以在网格项目上固定大小的地方使用。我仍然无法完全理解 minmax()
的工作原理——我玩弄它的时间越多,就越不理解它——但它在这种情况下看起来做的事情是创建网格,然后均匀拉伸其列,直到它们填充所有可用空间
grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
我们在这里可以做的另一件很酷的事情是防止图像溢出,即使它比网格元素更宽。我们可以用 min(8em, 100%)
替换最小 8em
来做到这一点。这基本上确保了图像永远不会超过 100%,但永远不会低于 8em。感谢 Chris 的建议!
请注意,min()
函数在预 Chromium Edge 中不起作用!
请记住,只有当所有图像具有相同的纵横比时,此方法才能产生理想的结果——就像我在这里使用的正方形图像一样。对于我的博客,这不是问题,因为所有照片都是用我的索尼爱立信 W800i 手机拍摄的,它们都具有相同的纵横比。但是,如果我们放入具有不同纵横比的图像,网格将不再那么美观。
当然,我们可以将图像的 `height` 设置为固定值,但这会扭曲图像……除非我们将 object-fit
设置为 `cover`,这将解决我们的问题!
另一个想法是将第一个缩略图变成一种横幅,跨越所有网格列。唯一的问题是我们不知道列数,因为这取决于视窗。但是,有一个解决方案——我们可以将 `grid-column-end` 设置为 `-1`!
.grid--thumbs {
/* same styles as before */
a:first-child {
grid-column: 1/ -1;
img { height: 13em }
}
}
第一张图片比其他所有图片都具有更大的 `height`。
当然,如果我们希望图像跨越除最后一列以外的所有列,我们可以将其设置为 `-2`,依此类推……负列索引是有效的!
auto-fill
是我在 MDN 上注意到的另一个网格属性关键字。它们的解释都是没有视觉效果的长篇文字,因此我发现它们并不特别有用。更糟糕的是,在上面任何网格演示中将 `auto-fit` 替换为 `auto-fill` 没有任何区别。即使查看了 文章 或玩弄 示例,它们到底是如何工作的以及它们之间的区别仍然是一个谜。
然而,尝试不同的方法并在各种情况下查看结果,让我得出一个结论,如果我们使用的是 `minmax()` 列宽度而不是固定宽度(比如 `8em`),那么最好使用 `auto-fill` 而不是 `auto-fit`,因为如果我们碰巧只有几张图片,结果看起来会更好,如下面的交互式演示所示。
我认为我最喜欢的是最初的想法,即一个居中对齐且列宽基本固定的缩略图网格(但仍然使用 `min(100%, 15em)` 而不是仅仅 `15em`)。归根结底,这取决于个人喜好,下面的演示中所看到的内容恰好对我来说看起来更好。
我在这个演示中使用的是 `auto-fit`,因为它产生与 `auto-fill` 相同的结果,并且字符更少。但是,在制作这个演示时,我并不理解为什么这两个关键字会产生相同的结果,因为图库中的项目比我们填充一行所需的更多。
但一旦改变,`auto-fit` 和 `auto-fill` 就会产生不同的结果,如下所示。您可以更改 `justify-content` 值和放置在网格上的项目数量。
我并不确定哪个是更好的选择。我想这也要取决于个人喜好。与 `justify-content: center` 结合使用,`auto-fill` 似乎是更合乎逻辑的选择,但同时,`auto-fit` 却产生了更美观的结果。
我使用基于网格的布局遇到的唯一问题是,似乎没有办法在单元格之间添加任何类型的边框或分隔符(即,使用网格间隙中的空间插入线条或装饰器)。如果有人知道解决方法,请告诉我!
是的,这是一个相当严重的问题。我个人一直以不同的方式解决它,具体取决于具体情况。有时我会在容器上使用线性渐变,有时我会在网格项目上使用伪元素。我无法真正将这个问题视为一个具有通用解决方案的问题。
嗨,saltymouse,
如果你想要元素之间的边框,我建议将网格包装在一个 `overflow: hidden` 容器中,并将网格宽度设置为 100% + 1px,并为每个元素添加 `border-right` 和 `border-bottom`。我唯一遇到的问题是底部的边框。
Tim
哇!感谢你写了这篇很棒的文章,并分享你的经验。
此致。
有趣的文章,感谢你分享你的想法。不过,只有在你不需要支持该死的 IE11 的情况下,你的解决方案才能被称为理想。就我个人而言,对于这种布局,我会使用类似你的 Flexbox 方法,在容器末尾添加一些空 div,但略有不同。你需要将 `justify-content: center;` 添加到你的 `grid–thumbs` 元素中,并完全删除 `“a, div:empty {…}”` 规则集。
我的博客支持比 IE11 更久远的 IE 版本(我甚至在模拟模式下测试过 IE7),但支持并不意味着要让它拥有完全相同的精美外观。这意味着该网站完全可用,并且具有简洁、不破碎的外观。
网格布局和精美视觉效果都放在 `@supports (mix-blend-mode: screen) {}` 中,这意味着不支持此功能的浏览器(包括 IE 和 pre-Chromium Edge)只会获得浮动版本。网格在 IE/ pre-Chromium Edge 中不会居中对齐,但这又有什么关系呢?布局并没有因此而损坏。网站仍然可用且速度很快。
不久前,我在 Mondrian 笔记本上遇到了类似的问题 (https://codepen.io/dschoni/pen/GRovoxb),但我通过阅读 Jen Simmons @jensimmons 的一些文章解决了它。
网格居中,末尾的磁贴大部分是靠左的,如果空间足够的话。
磁贴可以具有不同的尺寸,并将根据最佳匹配进行排序。
当然,代码并不一定是最好的,但它适用于练习。