用 CSS 解决!基于给定元素数量的逻辑样式

Avatar of Una Kravets
Una Kravets

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

这篇文章是关于 CSS 强大功能的 系列文章 中的第三篇。

文章系列

  1. 彩色化 SVG 背景
  2. 下拉菜单
  3. 基于给定元素数量的逻辑样式(本篇文章)

您知道 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 指出,这是一种选择元素的较慢方法,因此请谨慎使用。