使用 CSS Grid 时,首先要做的是设置 display: grid
在我们想要成为网格容器的元素上。 然后我们使用以下组合显式定义网格 grid-template-columns
, grid-template-rows
及 grid-template-areas
. 从那里开始,下一步是将项目放置在网格内。
这是应该使用的经典方法,我也推荐它。 但是,还有另一种创建网格的方法 没有任何明确的定义. 我们称之为 隐式网格.
目录
“显式的,隐式的? 这里到底是怎么回事?”
奇怪的术语,对吧? 曼努埃尔·马图佐维奇已经 具有 很好的解释 我们可以通过 CSS Grid 中的“隐式”和“显式”来了解什么,但让我们直接深入了解 此 specification 说:
grid-template-rows
,grid-template-columns
及grid-template-areas
属性定义形成的固定数量的轨道 显式网格. 当网格项目位于这些边界之外时,网格容器会通过向网格添加隐式网格线来生成隐式网格轨迹。 这些线连同显式网格形式 隐式网格.
因此,简单来说,浏览器会自动生成额外的行和列,以防任何元素碰巧放置在定义的网格之外。
自动放置呢?
类似于隐式网格的概念, 自动放置 是浏览器自动将项目放置在网格内的能力。 我们并不总是需要给出每个项目的位置。
通过不同的用例,我们将看到这些特性如何帮助我们用几行代码创建复杂和动态的网格。
动态侧边栏
在这里,我们有三种不同的布局,但我们只有一种适用于所有布局的网格配置。
main {
display: grid;
grid-template-columns: 1fr;
}
只有一列占用了所有可用空间。 这是我们的“显式”网格。 它被设置为适合一个网格项目 main
网格容器。 就这样。 一列一行:
但是如果我们决定把另一个元素放在那里,比如说 aside
(我们的动态侧边栏)。 正如当前(并且明确)定义的那样,我们的网格必须自动调整以找到该元素的位置。 如果我们对 CSS 什么都不做,这就是 DevTools 告诉我们正在发生的事情。
我们可以移动 <aside>
到旁边的列 <section>
:
aside {
grid-column-start: 2;
}
这就是 DevTools 现在告诉我们的内容:
我们将元素放在第二列,但是……我们没有第二列。 很奇怪,对吧? 我们从未在 <main>
网格容器,但浏览器为我们创建了一个! 这是我们查看的规范中的关键部分:
当网格项目位于这些边界之外时,网格容器会通过向网格添加隐式网格线来生成隐式网格轨迹。
这个强大的功能使我们能够拥有动态布局。 如果我们只有 <section>
元素,我们得到的只是一列。 但是如果我们添加一个 <aside>
混合元素,创建一个额外的列来包含它。
我们可以将 <aside>
前 <section>
而是像这样:
aside {
grid-column-end: -2;
}
这将在网格的开头创建隐式列,这与之前将隐式列放在末尾的代码不同。
我们可以使用 grid-auto-flow
属性来设置任何和所有隐式轨道在一个 column
方向:
现在无需指定 grid-column-start
放置 <aside>
右边的元素 <section>
! 事实上,我们决定随时放入其中的任何其他网格项目现在都将沿列方向流动,每个项目都放置在其自己的隐式网格轨道中。 非常适合事先不知道网格中项目数量的情况!
也就是说,我们仍然需要 grid-column-end
如果我们想把它放在它左边的列中,因为否则, <aside>
将占据显式列,进而推动 <section>
在显式网格之外并强制它采用隐式列。
我知道我知道。 这有点令人费解。 这是另一个我们可以用来更好地理解这个小怪癖的例子:
在第一个示例中,我们没有指定任何展示位置。 在这种情况下,浏览器将首先放置 <aside>
显式列中的元素,因为它在 DOM 中排在第一位。 这 <section>
同时,它会自动放置在浏览器自动(或隐式)为我们创建的网格列中。
在第二个例子中,我们设置 <aside>
显式网格之外的元素:
aside {
grid-column-end: -2;
}
现在没关系 <aside>
首先出现在 HTML 中。 通过重新分配 <aside>
在其他地方,我们制作了 <section>
可用于显式列的元素。
图像网格
让我们尝试一些不同的图像网格,其中我们有一个大图像和一些缩略图在它旁边(或在它下面)。
我们有两种网格配置。 但猜猜怎么了? 我根本没有定义任何网格! 我正在做的就是:
.grid img:first-child {
grid-area: span 3 / span 3;
}
令人惊讶的是,我们只需要一行代码就可以完成这样的事情,所以让我们剖析正在发生的事情,你会发现它比你想象的要容易。 首先, grid-area
是一个简写属性,它将以下属性组合成一个声明:
grid-row-start
grid-row-end
grid-column-start
grid-column-end
等待! 不是吗
grid-area
我们用来定义的属性 命名区域 而不是元素在网格上的开始和结束位置?
是的,但它也做得更多。 我们可以写更多关于 grid-area
,但在这种特殊情况下:
.grid img:first-child {
grid-area: span 3 / span 3;
}
/* ...is equivalent to: */
.grid img:first-child {
grid-row-start: span 3;
grid-column-start: span 3;
grid-row-end: auto;
grid-column-end: auto;
}
我们在破解打开 DevTools 扩展简写版本时可以看到同样的事情:
这意味着网格中的第一个图像元素需要跨越 三栏 和 三排. 但是由于我们没有定义任何列或行,浏览器会为我们做这件事。
我们基本上将第一张图片放在 HTML 中以占据 3⨉3 的网格。 这意味着任何其他图像都将自动放置在相同的三列中,而无需指定任何新内容。
总而言之,我们告诉浏览器,第一个图像需要占用我们在设置网格容器时从未明确定义的三列和三行空间。 浏览器为我们设置了这些列和行。 因此, HTML 中的其余图像使用相同的三列和三行直接流动到位. 由于第一张图像占据了第一行的所有三列,其余图像流入额外的行,每行包含三列,其中每个图像占据一列。
所有这一切都来自一行 CSS! 这就是“隐式”网格和自动放置的力量。
对于该演示中的第二个网格配置,我所做的只是使用更改自动流向 grid-auto-flow: column
与我们之前放置 <aside>
a 旁边的元素 <section>
. 这会强制浏览器创建一个 第四 它可以用来放置剩余图像的列。 由于我们有三行,剩余的图像被放置在同一垂直列中。
我们需要为图像添加一些属性,以确保它们很好地适合网格内而不会溢出:
.grid {
display: grid;
grid-gap: 10px;
}
/* for the second grid configuration */
.horizontal {
grid-auto-flow: column;
}
/* The large 3⨉3 image */
.grid img:first-child {
grid-area: span 3 / span 3;
}
/* Help prevent stretched or distorted images */
img {
width: 100%;
height: 100%;
object-fit: cover;
}
当然,我们可以通过调整一个值轻松更新网格以考虑更多图像。 那将是 3
在大图像的样式中。 我们有这个:
.grid img:first-child {
grid-area: span 3 / span 3;
}
但是我们可以简单地添加第四列,只需将其更改为 4
代替:
.grid img:first-child {
grid-area: span 4 / span 4;
}
更好的是:让我们将其设置为自定义属性,以使更新更容易。
动态布局
侧边栏的第一个用例是我们的第一个动态布局。 现在我们将处理更复杂的布局,其中元素的数量将决定网格配置。
在这个例子中,我们可以有 XNUMX 到 XNUMX 个元素,其中网格的调整方式可以很好地适应元素的数量,而不会留下任何尴尬的间隙或缺失的空间。
当我们只有一个元素时,我们什么也不做。 该元素将拉伸以填充网格自动创建的唯一行和列。
当我们添加第二个元素时,我们使用创建另一个(隐式)列 grid-column-start: 2
.
当我们添加第三个元素时,它应该占据两列的宽度——这就是我们使用的原因 grid-column-start: span 2
,但前提是它是 :last-child
因为如果(以及何时)我们添加第四个元素,则该元素应该只占用一列。
加起来,我们有 四种网格配置 仅 两个声明 和隐式网格的魔力:
.grid {
display: grid;
}
.grid :nth-child(2) {
grid-column-start: 2;
}
.grid :nth-child(3):last-child {
grid-column-start: span 2;
}
让我们尝试另一个:
对于只有一个或两个元素的第一种和第二种情况,我们什么也不做。 但是,当我们添加第三个元素时,我们会告诉浏览器——只要它是 :last-child
— 它应该跨越两列。 当我们添加第四个元素时,我们告诉浏览器该元素需要放在第二列。
.grid {
display: grid;
}
.grid :nth-child(3):last-child {
grid-column-start: span 2;
}
.grid :nth-child(4) {
grid-column-start: 2;
}
你开始掌握诀窍了吗? 我们根据元素的数量给浏览器特定的指令(使用 :nth-child
),有时,一条指令可以完全改变布局。
需要注意的是,当我们处理不同的内容时,大小不会相同:
由于我们没有为我们的项目定义任何尺寸,浏览器会根据它们的内容自动为我们调整它们的尺寸,我们最终可能会得到与我们刚刚看到的不同的尺寸。 为了克服这个问题,我们必须 明确地 指定所有列和行的大小相同:
grid-auto-rows: 1fr;
grid-auto-columns: 1fr;
嘿,我们还没有玩过这些属性! grid-auto-rows
和 grid-auto-columns
在网格容器中分别设置隐式行和列的大小。 或者,作为 规范 解释一下:
grid-auto-columns
和grid-auto-rows
属性指定未分配大小的轨道的大小grid-template-rows
orgrid-template-columns
.
这是另一个示例,我们最多可以使用六个元素。 这次我会让你剖析代码。 别担心,选择器可能看起来很复杂,但逻辑非常简单。
即使有六个元素,我们也只需要两个声明。 想象一下我们可以用几行代码实现的所有复杂和动态的布局!
这是怎么回事
grid-auto-rows
为什么它需要三个值? 我们是否定义了三行?
不,我们没有定义三行。 但是我们 ,那恭喜你, 定义三个值作为我们隐式行的模式。 逻辑如下:
- 如果我们有一行,它将使用第一个值调整大小。
- 如果我们有两行,第一行获取第一个值,第二行获取第二个值。
- 如果我们有三行,则将使用这三个值。
- 如果我们有四行(有趣的部分来了),我们将三个值用于前三行,然后将第一个值再次用于第四行。 这就是为什么我们会重复这种模式来调整所有隐式行的大小。
- 如果我们有 100 行,它们的大小将是 XNUMX×XNUMX
2fr 2fr 1fr 2fr 2fr 1fr 2fr 2fr 1fr
等等。
不比 grid-template-rows
它定义了行数及其大小, grid-auto-rows
只有在此过程中可能创建的尺寸行。
如果我们回到我们的示例,逻辑是在创建两行时具有相等的大小(我们将使用 2fr 2fr
),但是如果创建了第三行,我们会使其更小一些。
网格图案
对于最后一个,我们将讨论模式。 您可能已经看到了两列布局,其中一列比另一列宽,并且每一行交替这些列的位置。
如果不知道我们正在处理多少内容,这种排序布局也很难实现,但是 CSS Grid 的隐式自动放置功能使其相对容易。
看一眼代码。 它可能看起来很复杂,但让我们分解一下,因为它非常简单。
首先要做的是识别模式。 问问自己:“图案应该重复多少个元素?” 在这种情况下,它位于每四个元素之后。 所以,让我们看看现在只使用四个元素:
现在,让我们定义网格并使用 :nth-child
用于在元素之间交替的选择器:
.grid {
display: grid;
grid-auto-columns: 1fr; /* all the columns are equal */
grid-auto-rows: 100px; /* all the rows equal to 100px */
}
.grid :nth-child(4n + 1) { /* ?? */ }
.grid :nth-child(4n + 2) { /* ?? */ }
.grid :nth-child(4n + 3) { /* ?? */ }
.grid :nth-child(4n + 4) { /* ?? */ }
我们说过我们的模式每四个元素重复一次,所以我们将在逻辑上使用 4n + x
哪里 x
范围从 1 到 4。用这种方式解释模式更容易一些:
4(0) + 1 = 1 = 1st element /* we start with n = 0 */
4(0) + 2 = 2 = 2nd element
4(0) + 3 = 3 = 3rd element
4(0) + 4 = 4 = 4th element
4(1) + 1 = 5 = 5th element /* our pattern repeat here at n = 1 */
4(1) + 2 = 6 = 6th element
4(1) + 3 = 7 = 7th element
4(1) + 4 = 8 = 8th element
4(2) + 1 = 9 = 9th element /* our pattern repeat again here at n = 2 */
etc.
完美,对吧? 我们有四个元素,并在第五个元素、第九个元素等上重复模式。
那些 :nth-child
选择器可能很棘手! 克里斯有一个超级乐于助人的 解释这一切是如何运作的,包括 创建不同模式的食谱.
现在我们配置每个元素,以便:
- 第一个元素需要占用两列并从第一列开始(
grid-column: 1/span 2
). - 第二个元素放在第三列 (
grid-column-start: 3
). - 第三个元素放在第一列:(
grid-column-start: 1
). - 第四个元素占用两列并从第二列开始:(
grid-column: 2/span 2
).
这是在CSS中:
.grid {
display: grid;
grid-auto-columns: 1fr; /* all the columns are equal */
grid-auto-rows: 100px; /* all the rows are equal to 100px */
}
.grid :nth-child(4n + 1) { grid-column: 1/span 2; }
.grid :nth-child(4n + 2) { grid-column-start: 3; }
.grid :nth-child(4n + 3) { grid-column-start: 1; }
.grid :nth-child(4n + 4) { grid-column: 2/span 2; }
我们可以在这里停下来完成……但我们可以做得更好! 具体来说,我们可以删除一些声明并依靠网格的自动放置功能为我们完成这项工作。 这是最难理解的部分,需要大量练习才能确定可以删除的内容。
我们能做的第一件事就是更新 grid-column: 1 /span 2
并且只使用 grid-column: span 2
因为默认情况下,浏览器会将第一项放入第一列。 我们也可以删除这个:
.grid :nth-child(4n + 3) { grid-column-start: 1; }
通过放置第一个、第二个和第四个项目,网格会自动将第三个项目放置在正确的位置。 这意味着我们只剩下这个:
.grid {
display: grid;
grid-auto-rows: 100px; /* all the rows are equal to 100px */
grid-auto-columns: 1fr; /* all the columns are equal */
}
.grid :nth-child(4n + 1) { grid-column: span 2; }
.grid :nth-child(4n + 2) { grid-column-start: 2; }
.grid :nth-child(4n + 4) { grid-column: 2/span 2; }
但是来吧,我们可以漫步做得更好! 我们也可以删除这个:
.grid :nth-child(4n + 2) { grid-column-start: 2; }
为什么? 如果我们将第四个元素放在第二列中,同时允许它占据两个完整的列,我们会强制网格创建第三个隐式列,从而在没有明确告诉它的情况下给我们总共三列。 第四个元素不能进入第一行,因为第一项也占用两列,所以它流到下一行。 这种配置在第一行留下一个空列,在第二行留下一个空列。
我想你知道故事的结局。 浏览器会自动将第二个和第三个项目放置在那些空白处。 所以我们的代码变得更加简单:
.grid {
display: grid;
grid-auto-columns: 1fr; /* all the columns are equal */
grid-auto-rows: 100px; /* all the rows are equal to 100px */
}
.grid :nth-child(4n + 1) { grid-column: span 2; }
.grid :nth-child(4n + 4) { grid-column: 2/span 2; }
只需五个声明即可创建一个非常酷且非常灵活的模式。 优化部分可能很棘手,但是您会习惯它并通过练习获得一些技巧。
为什么不用
grid-template-columns
因为我们知道列数,所以要定义显式列?
我们能做到这一点! 这是它的代码:
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr); /* all the columns are equal */
grid-auto-rows: 100px; /* all the rows are equal to 100px */
}
.grid :nth-child(4n + 1),
.grid :nth-child(4n + 4) {
grid-column: span 2;
}
如您所见,代码肯定更直观。 我们定义了三个明确的网格列,并告诉浏览器第一个和第四个元素需要占用两列。 我强烈推荐这种方法! 但本文的目标是探索我们从 CSS Grid 的隐式和自动放置功能中获得的新想法和技巧。
显式方法更直接,而隐式网格需要你——原谅双关语——填补 CSS 在幕后做额外工作的空白。 最后,我相信对隐式网格有一个扎实的理解会帮助你更好地理解 CSS Grid 算法。 毕竟,我们不是来研究显而易见的东西——我们来这里是为了探索荒野!
让我们尝试另一种模式,这次更快一点:
我们的模式每六个元素重复一次。 第三个和第四个元素都需要占据整整两行。 如果我们放置第三个和第四个元素,似乎我们不需要接触其他的,所以让我们尝试以下操作:
.grid {
display: grid;
grid-auto-columns: 1fr;
grid-auto-rows: 100px;
}
.grid :nth-child(6n + 3) {
grid-area: span 2/2; /* grid-row-start: span 2 && grid-column-start: 2 */
}
.grid :nth-child(6n + 4) {
grid-area: span 2/1; /* grid-row-start: span 2 && grid-column-start: 1 */
}
嗯,不好。 我们需要将第二个元素放在第一列。 否则,网格将自动将其放置在第二列中。
.grid :nth-child(6n + 2) {
grid-column: 1; /* grid-column-start: 1 */
}
更好,但还有更多工作要做,我们需要将第三个元素移到顶部。 尝试以这种方式将其放在第一行是很诱人的:
.grid :nth-child(6n + 3) {
grid-area: 1/2/span 2;
/* Equivalent to:
grid-row-start: 1;
grid-row-end: span 2;
grid-column-start: 2
*/
}
但这不起作用,因为它迫使所有 6n + 3
元素被放置在同一区域,这使得布局混乱。 真正的解决方案是保留第三个元素的初始定义并添加 grid-auto-flow: dense
填补空白。 来自 MDN:
[]“密集”包装算法试图填充 在网格中较早的孔中,如果稍后出现较小的项目。 这可能会导致项目出现乱序,这样做会填补较大项目留下的漏洞。 如果省略,则使用“稀疏”算法,其中放置算法仅在放置项目时在网格中“向前”移动,从不回溯以填充孔。 这确保了所有自动放置的项目都“按顺序”显示,即使这会留下可能被以后的项目填补的漏洞。
我知道这个属性不是很直观,但是当您遇到放置问题时永远不要忘记它。 在徒劳地尝试不同的配置之前,请添加它,因为它可以修复您的布局而无需额外的努力。
为什么不总是默认添加这个属性?
我不推荐它,因为在某些情况下,我们不希望这种行为。 请注意 MDN 的解释如何提到它会导致项目“无序”流动以填补较大项目留下的漏洞。 视觉顺序通常与源顺序一样重要,尤其是在涉及可访问界面时,以及 grid-auto-flow: dense
有时会导致视觉顺序和源顺序不匹配。
我们的最终代码是:
.grid {
display: grid;
grid-auto-columns: 1fr;
grid-auto-flow: dense;
grid-auto-rows: 100px;
}
.grid :nth-child(6n + 2) { grid-column: 1; }
.grid :nth-child(6n + 3) { grid-area: span 2/2; }
.grid :nth-child(6n + 4) { grid-row: span 2; }
另一个? 我们走吧!
对于这一点,我不会说太多,而是向您展示我使用的代码的插图。 试着看看你是否明白我是如何到达那个代码的:
黑色的项目隐式放置在网格中。 应该注意的是,与我到达那里的方式相比,我们可以通过更多方式获得相同的布局。 你也能弄清楚这些吗? 怎么用 grid-template-columns
? 在评论部分分享你的作品。
我会给你留下最后一个模式:
我做的 有解决办法 对于这个,但轮到你练习了。 把我们学到的所有东西都尝试自己编写代码,然后与我的解决方案进行比较。 如果您以冗长的内容结尾,请不要担心——最重要的是找到一个可行的解决方案。
想要更多?
在我们结束之前,我想分享一些与 CSS Grid 相关的 Stack Overflow 问题,我在其中加入了使用我们在这里一起讨论的许多技术的答案。 这是一个很好的列表,它显示了有多少实际用例和实际情况出现在这些东西派上用场的地方:
结束了
CSS Grid 已经存在很多年了,但是仍然有很多鲜为人知和使用过的技巧没有被广泛讨论。 隐式网格和自动放置功能就是其中两个!
是的,这可能会变得具有挑战性! 我花了很多时间来理解隐式网格背后的逻辑,但我仍然在为自动放置而苦恼。 如果您想花更多的时间来了解显式和隐式网格,这里有一些额外的解释和示例值得一试:
同样,您可能想阅读 grid-auto-columns
在 CSS-Tricks Almanac 中,因为 Mojtaba Seyedi 非常详细,并包含非常有用的视觉效果来帮助解释行为。
就像我在开始时所说的那样,我们在这里介绍的方法并不是要取代您已经知道的用于构建网格的常用方法。 我只是在探索在某些情况下可能会有所帮助的不同方法。