使用生成的内容和 CSS 网格布局为空单元格设置样式
已发表: 2022-03-10一个常见的 Grid Layout 问题是布局方法的新手想知道如何设置不包含任何内容的网格单元格的样式。 在当前的 1 级规范中,这是不可能的,因为无法定位空的网格单元或网格区域并应用样式。 这意味着要应用样式,您需要插入一个元素。
在本文中,我将了解如何使用 CSS 生成的内容来实现空单元格的样式,而不添加多余的空元素,并展示一些使用这种技术的用例。
仔细看看 BFC
如果您曾经使用 CSS 制作过布局,那么您可能知道 BFC 是什么。 了解它为什么起作用以及如何创建它很有用,并且可以帮助您了解布局在 CSS 中的工作原理。 阅读相关文章 →
为什么我们不能为空白区域设计样式?
网格规范的开头段落说,
“这个 CSS 模块定义了一个基于网格的二维布局系统,针对用户界面设计进行了优化。 在网格布局模型中,网格容器的子项可以定位到预定义的灵活或固定大小布局网格中的任意槽中。”
这里的关键词是“网格容器的子级”。 该规范定义了在父元素上创建网格,子项可以定位到其中。 它没有定义该网格的任何样式,甚至没有实现像我们在多列布局中拥有的column-rule
属性之类的东西。 我们为子项设置样式,而不是网格本身,这使我们需要有某种元素来应用该样式。
使用冗余元素作为样式挂钩
插入样式的一种方法是在文档中插入冗余元素,例如 span 或 div。 开发人员往往不喜欢这个想法,尽管他们多年来一直在添加额外的冗余“行包装器”以使用浮动实现网格布局。 也许那个明显的空元素比包装元素的隐藏冗余更令人反感!
完全空的元素成为网格项,并且可以像包含内容的元素一样添加背景和边框,如本示例所示。
Eric Meyer 在他的 A List Apart 文章 Faux Grid Tracks 中主张使用b
元素作为您选择的冗余元素,因为它不赋予任何语义含义,并且在标记中作为一个钩子很短而且相当明显。
插入额外的几个div
或b
元素不太可能是对你曾经犯过的良好标记的最大犯罪,所以如果需要,我不会因为选择这种方法而失眠。 Web 开发经常涉及选择最不次优的方法来完成工作,直到设计出更好的解决方案。 但是,如果可能的话,我更喜欢将我的样式保存在一个地方,安全地保存在样式表中。 如果不出意外,它可以更轻松地重用样式,而无需担心额外的必需标记。 正是出于这个原因,我倾向于查看生成的内容,这是我在使用 CSS 格式化书籍所做的工作中非常熟悉的内容,您大部分时间都在使用此功能。
使用生成的内容作为样式挂钩
CSS Generated Content 使用::before
和::after
CSS 伪类以及content
属性将某种内容插入到文档中。 插入内容的想法可能会让您认为这是用于插入文本,虽然这是可能的,但出于我们的目的,我们有兴趣插入一个空元素作为我们网格容器的直接子元素。 插入元素后,我们可以对其进行样式设置。
在下面的示例中,我有一个包含元素,它将成为我的网格容器,其中嵌套了另一个元素。 这个单一的直接孩子将成为一个网格项目。 我在容器上定义了一个三列三行网格,然后使用网格线定位单个项目,因此它位于中间的网格单元中。
<div class="grid"> <div class="item"></div> </div>
.grid { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px 100px; grid-gap: 10px; } .grid > * { border: 2px solid rgb(137,153,175); } .item { grid-column: 2; grid-row: 2; }
如果我们看一下这个例子,使用 Firefox Grid Inspector 覆盖网格线,我们可以看到网格的其他空单元格是如何存在的,但是,要为它们添加背景或边框,我们需要添加额外的子元素元素。 这正是生成的内容所支持的。
在我的 CSS 中,我添加了一个空字符串::before
和::after
我的网格容器。 这些将立即成为网格项目并拉伸以填充其容器。 然后我添加我需要的框样式,在本例中添加背景颜色,并像放置任何常规网格项一样定位它们。
.grid::before { content: ""; background-color: rgb(214,232,182); grid-column: 3; grid-row: 1; } .grid::after { content: ""; background-color: rgb(214,232,182); grid-column: 1; grid-row: 3; }
在文档中,我们仍然只有一个子元素,多余的样式元素包含在 CSS 中,这似乎完全合理,因为它们仅用于样式目的。
生成内容方法的局限性
如果您决定同时设置右上角和左下角网格单元格的样式,则这种方法的明显问题将变得明显。 您只能将生成的内容应用到容器的顶部和容器的底部,并且不允许使用多个::before
和::after
伪元素。 如果你想自己创建一个 CSS Grid 棋盘格,那么这个方法是行不通的! 如果你发现你确实需要做很多空单元格样式,那么在可预见的未来,上面解释的“填充 B”方法可能是你最好的选择。
生成的内容方法也可能使未来从事您项目的开发人员感到困惑。 当我们以容器为目标时,如果您在其他地方重用该类,它将带来生成的内容,如果这是您想要的,这将很有用。 在下一个示例中,我们在标题的任一侧添加了装饰线, h1
的每个实例都有这些线条是合理的。 但是,如果您不知道会发生这种情况,那将是非常令人困惑的! 容器规则上方的注释行将在这里有所帮助。 这些天我倾向于在模式库中工作,这确实有助于将这些组件整齐地放在一个地方,从而更清楚地将类应用于元素时会发生什么。
花式标题
我最喜欢的生成内容技巧之一是设置标题样式。 过去,我不得不放弃需要额外包装器和绝对定位轨道才能实现的标题样式。 当内容来自 CMS 时,通常不可能添加那些多余的包装器。
使用 Grid 和生成的内容,我们可以在标题的任一侧添加一行,而无需添加任何额外的标记。 该行将根据可用空间扩大和缩小,当 Grid 在浏览器中不可用时,它将优雅地退回到一个普通的居中标题。
我们的标记是一个简单的h1
。
<h1>My heading</h1>
在h1
的规则中,我创建了一个三列网格。 grid-template-columns
的值给出1fr
的轨道,然后是auto
和最终轨道1fr
。 两个1fr
轨道将在航向占用了它需要位于auto
尺寸轨道内的空间后共享剩余的可用空间。
我添加了值为center
的text-align
属性,以便在没有网格的浏览器中输入我的标题。
h1 { text-align: center; display: grid; grid-template-columns: 1fr auto 1fr; grid-gap: 20px; }
我们现在添加我们生成的内容,在标题文本之前和之后添加一行。 我将这些规则包装在一个特征查询中,所以我们不会在没有网格布局的浏览器中得到任何奇怪的生成内容。
线本身是生成项目的边框。
@supports (display: grid) { h1:before, h1:after { content: ""; align-self: center; border-top: 1px solid #999; } }
这就是你需要做的! 您可以使用相同的技术在元素的上方或下方添加任何样式,甚至添加图标。 通过将您的项目放入单独的轨道,您知道该项目最终不可能与您的标题文本重叠,这在尝试使用绝对定位执行此类事情时往往是问题。 您还可以从网格中的项目相互对齐的精确方式中受益。
这是一个很好的例子,可以使用网格布局进行增强,即使您还没有准备好直接使用网格进行重大重新设计,您也可以利用它。 它很好地回到了一个简单的标题,支持浏览器的人得到了额外的接触,每个人都得到了内容。 Eric Meyer 采用了类似的方法,使用生成的内容向块引用元素添加易于设置样式和定位的引号。
有了这些小功能,我通常不会一开始就想到要使用网格布局。 当我开始弄清楚如何实现我的设计时,我意识到这是选择的布局方法。 正是出于这个原因,我鼓励人们不要将 Grid 视为用于组件之上的页面布局,如果您这样做,您可能会错过很多它可以提供帮助的机会。
为您的设计区域添加背景和边框
我们还可以使用生成的内容来堆叠物品; 事实上,多个项目可以占据一个特定的网格单元。 这可以包括与生成的内容一起插入的那些项目。
在下一个示例中,我有一个包含两个内容部分和一个全角项目的设计。 内容后面是一个背景,它也在全角项目下方。
标记有一个容器,其中部分和全角元素作为直接子元素,我使用基于行的放置将我的项目放置到网格上。
<article class="grid"> <section class="section1"> <p>…</p> </section> <div class="full-width"> <img src=“placeholder.jpg” alt=“Placeholder”> </div> <section class="section2"> <p>…</p> </section> </article>
.grid { display: grid; grid-template-columns: 1fr 20px 4fr 20px 1fr; grid-template-rows: auto 300px auto; grid-row-gap: 1em; } .section1 { grid-column: 3; grid-row: 1; } .section2 { grid-column: 3; grid-row: 3; } .full-width { grid-column: 1 / -1; grid-row: 2; background-color: rgba(214,232,182,.5); padding: 20px 0; }
这为我提供了全角图像和放置两个内容部分的布局; 但是,如果我将背景添加到部分,它将停止在section
和全宽图像之间的row-gap
上方。
.section { background-color: rgba(214,232,182,.3); border: 5px solid rgb(214,232,182); }
如果我们移除grid-row-gap
并使用 padding 来制造空间,它仍然不会启用在全宽面板下方运行的背景效果。
这是我们可以使用生成内容的地方。 我在网格容器::before
并给它一个背景颜色。 如果我什么都不做,这会将内容定位在网格的第一个单元格中。
.grid::before { content: ""; background-color: rgba(214,232,182,.3); border: 5px solid rgb(214,232,182); }
然后,我可以使用基于行的定位来定位内容,以覆盖应显示背景颜色的区域。
.grid::before { content: ""; background-color: rgba(214,232,182,.3); border: 5px solid rgb(214,232,182); grid-column: 2 / 5; grid-row: 1 / 4; }
您可以在此 CodePen 中查看完整示例。
使用z-index
控制堆栈
在上面的示例中,生成的内容使用::before
插入。 这意味着其他元素在它之后,它位于堆栈的底部,因此将显示在我想要的其余内容的后面。 您还可以使用z-index
来控制堆栈。 尝试将::before
选择器更改为::after
。 生成的内容背景现在位于所有内容之上,正如您可以从边框在图像上运行的方式看到的那样。 这是因为它现在已成为网格容器中的最后一个东西,它是最后绘制的,因此出现在“顶部”。
要改变这一点,你需要给这个元素一个比其他元素更低的z-index
属性。 如果没有其他东西具有z-index
值,那么最简单的做法是为生成的内容提供-1
的z-index
。 这将使它成为堆栈中的第一件事,作为具有最低z-index
的项目。
.grid::after { z-index: -1; content: ""; background-color: rgba(214,232,182,.3); border: 5px solid rgb(214,232,182); grid-column: 2 / 5; grid-row: 1 / 4; }
以这种方式添加背景不必局限于将背景完全放在内容后面。 能够在设计的一部分后面弹出色块可以产生一些有趣的效果。
这是规范将来可能解决的问题吗?
添加背景和边框确实感觉像是 CSS Grid 规范中缺少的一个特性,并且工作组已经与社区的许多成员一起讨论过这个特性(讨论线程在 GitHub 上)。
如果您的用例无法通过生成的内容轻松解决,请将您的想法添加到该线程中。 您的评论和用例有助于证明开发人员对该功能感兴趣,并确保任何提案都涵盖了您需要做的事情。
请提供更多示例!
如果本文鼓励您尝试生成的内容,或者如果您已经有示例,请将其添加到评论中。 每个人都是在生产中使用 Grid 的新手,所以有很多,“我从没想过! ” 当我们将 Grid 与其他布局方法相结合时,我们将有机会拥有。