这篇文章是关于 CSS 强大功能的 系列文章 中的第三篇。
文章系列
- 彩色化 SVG 背景
- 下拉菜单
- 基于给定元素数量的逻辑样式(本篇文章)
您知道 CSS 是图灵完备的 吗?您知道它可以用来实现一些非常严重的逻辑样式吗?是的,您可以!您不必在 JavaScript 中设置所有基于逻辑的样式规则,甚至不必使用 JavaScript 来设置您要针对其进行样式设置的类。在许多情况下,CSS 可以自行处理。我每天都在发现新的 CSS 技巧,这让我更加爱上它。
今年,我开始在 Bustle Digital Group 工作。在媒体领域,就像许多产品一样,工程团队构建了一个应该支持所有用例的平台。我们的 CMS 为作者和编辑提供了创建文章以及策划页面和控制广告注入的功能。
与处理静态网站不同,工程团队无法完全控制用户输入的数据,因此必须针对良好的用户体验做出设计决策和制定管理规则。我们在数字媒体领域遇到的某些场景真正激励我去研究如何使用 CSS 来解决这些 UI 挑战,也正是那时,涉及这种想法的解决方案才真正进入我的视野。
让我们看看一些示例吧!
示例 1:二元状态
一个经常被遗忘并且非常有用的选择器是 :empty
伪选择器。它允许您根据元素是否包含任何内容或是否不包含任何内容来设置元素的样式。欢迎 空状态!空状态是与用户互动并展示应用程序个性的一种好方法,您可以直接从 CSS 中注入个性。
在本示例中,我们有来自用户的任何列表。这可能是用户发布的帖子(作为作者)或用户保存的书签文章(作为编辑)。这里用例实际上是无穷无尽的。我们可以使用伪元素注入图像、样式和文本,而不是注入 JavaScript。

这里我们的解决方案仅仅只有三行代码
div:empty:after {
content: 'oh no...';
}
您还可以添加一个 :before
伪元素来注入图像或您可能想要的任何其他内容。或者,可以使用 :not
伪选择器结合 :empty
来创建一个 :not(:empty)
规则并设置所有非空元素的样式,因此这些元素包含子元素。
查看 Una Kravets(@una)在 CodePen 上创建的 Pen 空状态。
注意:此演示仅用于显示目的。出于可访问性目的,不建议将内容放在伪元素中。您可以使用相同的针对 :empty
或 :not(:empty)
元素的技术来将样式应用于对屏幕阅读器更易访问的子元素。
高级数字选择
这是一个很好的入门示例,但我们可以比 CSS 中的子元素的这种二元选择复杂得多,为此,我们将使用 :nth-child
伪选择器!CSS-Tricks 有一个 很棒的工具 可以帮助您测试和玩转 :nth-child
选择,因为它确实可以派上用场,因为其中一些示例将向您展示这一点。
但在我们进入这些内容之前,它到底是如何工作的呢?
代码的核心部分是这部分,其中 div
代表任何给定的同级元素,而 x
代表我们用来确定样式断点的数字
div:first-child:nth-last-child(n + x),
div:first-child:nth-last-child(n + x) ~ div
使用 :nth-last-child
而不是使用 :nth-child
进行选择,使我们能够从系列的末尾开始,而不是从开头开始。当我们选择 :nth-last-child(n + x)
时,我们从末尾开始选择 x
值。如果 x = 3
,则看起来像这样

:nth-last-child(3)
如何从列表末尾选择第三个项目。现在,如果我们要计算 n + 3
的值,我们将在选择所有与3 或大于 3 的值匹配的项目(从末尾开始)。从 n = 0
开始(这意味着 0 + 3
,而第四个项目是 3
之后第一个从末尾开始的项目)。看起来像这样

:nth-last-child(n+3)
如何选择所有与 3 或大于 3 的值匹配的项目(从末尾开始)。这是一个很好的起点,但这里的想法是根据存在多少元素来有条件地设置所有元素的样式。因此,我们需要使用这些条件,但选择所有元素。让我们从选择第一个元素开始。我们需要创建一个条件来查看整个选择是否符合样式设置要求,然后从第一个同级元素开始

糟糕。我们目前只选择了第一个项目,而我们想要选择所有项目。幸运的是,我们可以为此使用非常方便的相邻同级选择器(~
)!

:first-child:last-child(n + 3) ~ *
将选择所有项目,除了第一个项目,就像我们想要的那样。好吧,现在您可以看到所有跟随第一个项目的项目都已选中,但我们缺少第一个项目,因此我们需要使用两个选择器,因此最终答案将是

示例 2:列表格式
假设您想在文章结尾列出一些鸣谢。您有一些空间需要填充,大多数文章只有少量鸣谢,但也有一些例外,这些文章制作价值很高,并且在制作过程中有很多人参与。我们希望确保这两种情况都具有良好的视觉体验,并且可以使用 CSS 单独实现这一点。
计划如下:如果有四个或更少的鸣谢,请以项目符号形式列出它们。让它们占用垂直空间以适当地填充块。当列出五个或更多个鸣谢时,让我们将该列表转换为水平格式,以免对读者造成过多的视觉负担。毕竟,这只是一个小小的鸣谢框!

我们可以查看我们拥有的元素数量,并将其设置为 block
元素,直到我们达到上限。在那之后,我们将切换到 inline
样式,并添加一个伪元素来在视觉上分隔数据。
/* 5 or more items display next to each other */
li:first-child:nth-last-child(n + 5),
li:first-child:nth-last-child(n + 5) ~ li {
display: inline;
}
/* Adds semicolon after each item except the last item */
li:first-child:nth-last-child(n + 5) ~ li::before {
content: ';';
margin: 0 0.5em 0 -0.75em;
}
:nth-first-child:nth-last-child(n + 5)
允许我们声明:“从第一个子元素开始,如果第一个子元素匹配具有五个或更多个同级元素,则将样式应用于该子元素及其后的每个同级元素。” 这很令人困惑吗?好吧,它是有效的。
li:first-child:nth-last-child(n + 5)
选择第一个列表项,而 li:first-child:nth-last-child(n + 5) ~ li
选择跟随初始列表项的每个列表项。
查看 Una Kravets(@una)在 CodePen 上创建的 Pen vrQBMv。
示例 3:条件轮播
使用这种技术,让我们设置一个响应式轮播。在较大的尺寸下,您希望它在其中有三个项目时位于页面中间。但是,当它有足够的项目来水平填充屏幕时,请将其左对齐,以便用户可以滚动浏览它。

我们可以做的事情是,将元素拉伸以适合屏幕,除非我们有太多元素,否则它们需要溢出。在那之后,让我们全力以赴地进行溢出,并通过用箭头表示可滚动性以及增加项目之间的边距来真正展示轮播功能。除此之外,让我们添加一个粘性箭头按钮,以显示我们可以滚动浏览元素,并且可以将 JavaScript 事件绑定到轮播滚动。
我们可以执行与上述相同的事情,就技术而言,但我们还将仅使用 first-child
来检测 arrow
div 并将其显示在 UI 中。HTML 代码将如下所示
<ul>
<li>
<div class="box">1</div>
</li>
<li>
<div class="box">2</div>
</li>
...
<button class="arrow">——></button>
</ul>
在 DOM 中有空元素不是理想的做法,但请跟我来。这仍然是一个聪明的技巧。我们将使用 visibility: hidden
样式化 .arrow
按钮,使其在 DOM 和屏幕阅读器中不可见,除非满足条件(在本例中,如果存在四个或更多项目)。此时,我们将为其提供可见的显示(display: block
),样式和位置。
li:first-child:nth-last-child(n + 5) ~ .arrow {
display: block;
position: sticky;
...
}
查看 CodePen 上 Una Kravets 的作品:盒子对齐 (@una)。
更多信息!
在我为这篇文章进行研究时,我发现 Heydon Pickering 发表了一篇关于这种技术的优秀文章,叫做数量查询,还有另一篇由Lea Verou 撰写的示例!在 Heydon 文章的评论区中,Paul Irish 指出,这是一种选择元素的较慢方法,因此请谨慎使用。
很棒的文章!我使用了相同的技巧来确保我的快捷栏风格良好。很棒的解决方案!
https://medium.com/@bramdijkhuis/context-aware-list-items-witch-css-well-sort-of-2a433500fb15
很棒的文章。学到了一个非常方便的 CSS 新技巧。感谢分享。
我讨厌做 HTML 标记吹毛求疵的人,但在最后的轮播示例中,你有一个“按钮”作为“ul”元素的直接子元素。据我了解,根据规范,只有“li”元素允许作为“ul”元素的直接子元素。
不错的文章,一如既往。我真的很喜欢你在做什么,以及你如何激励开发人员去探索和尝试新事物。
不过,你写道:“从倒数第四个开始,从 n = 1 开始”
这是不完全正确的,因为
n
实际上是从 0 开始,而不是 1,所以在本例中,样式将从倒数第三个元素开始应用,而不是从倒数第四个开始。规范中的引用:“…
:nth-last-child(an+b)
伪类符号表示文档树中在它之后有 an+b-1 个兄弟姐妹的元素,其中 n 的值为任何正整数或零。”(链接:https://drafts.csswg.org/selectors-3/#nth-last-child-pseudo)我现在正在更新!图形是正确的,因为它是在倒数第三个元素之后的第一个元素,也就是倒数第四个元素,但我在写
n = 1
时,应该写n = 0
我可能漏掉了什么,但我认为该图/图形/图像也不正确,因为样式将从倒数第三个元素开始应用,而不是从倒数第四个开始。因为它是这样计算的:0 + 3、1 + 3、2 + 3,等等…。
我做了一个小演示来演示它:https://goo.gl/LPoC42
:empty
选择器唯一的缺点是,当元素中包含的唯一内容是空格时,它无法匹配。因此,你的代码在这方面必须非常干净。类似于伪元素,你不能将其用于没有对应结束标签的标签。是的!这是真的。
进一步阅读:Heydon Pickering 的 ALA 文章《用于 CSS 的数量查询》https://alistapart.com/article/quantity-queries-for-css
最后一个示例中的标记不是有效的 HTML:
button
不允许作为ul
的子元素。“哦,不”状态不会被屏幕阅读器访问吗?
是的,因此在示例之后有注释。:)
Una,这里有一些很棒的东西。我一直在尝试解决这些问题。你在这里概述的示例解释得非常清楚,它让我从一个完全不同的角度看待它们,我对此非常感谢。谢谢!
我一开始很困惑,因为你说的是“相邻兄弟选择器”。那只会选择第二个元素。
你实际上指的是“通用兄弟选择器”(或根据选择器级别 4“后续兄弟”。)
没错,那是“~”。
除此之外:好技巧,感谢你的文章 :-)
你好。我想知道,上面的示例中
:first-child
选择器的作用是什么?我在 CodePen 上运行它们,将
:first-child
选择器删除,它们(看起来)表现得完全一样关于文章的最后部分
“在 Heydon 文章的评论区中,Paul Irish 指出,这是一种选择元素的较慢方法,因此请谨慎使用。”
这是 Paul 在上面提到的文章中包含的测试的 URL:http://output.jsbin.com/gozula/1/quiet 以及结果
1. div.box:not(:empty):last-of-type .title: 5.777099609375ms
2. .box–last > .title-container > .title: 3.202880859375ms
3. .box:nth-last-child(-n+1) .title: 6.501708984375ms
所以它不像几年前那样糟糕(#3 比 #1 慢 500 倍)。在 Chrome 70.0 上测试。
我将你的“示例 2”进一步扩展,在内联显示时,除了最后一个项目之外,所有项目都添加了分号。你当前的示例在所有项目之前添加了分号,除了第一个项目。布局、间距和换行结果导致的呈现效果不是最佳的。
第一个代码片段在
:empty
选择器之后应该有两个冒号。如下所示div:empty::after
两者都是有效的语法
https://mdn.org.cn/en-US/docs/Web/CSS/::after#Syntax