向 CSS 猫头鹰选择器致敬

2025-06-10

向 CSS 猫头鹰选择器致敬

我热爱 CSS,这早已不是什么秘密。几年前,我爱上了一个非常简单但功能强大的 CSS 选择器。当时,我正在将自己的 CSS 提升到一个新的高度。我了解 CSS 的优先级和级联。无论是从零开始还是借助框架,我都能轻松上手 CSS。但后来,我遇到了一些用于复杂解决方案的新 CSS 选择器。因此,我不得不扩展我的 CSS 知识。

我一开始在网上寻找资源,比如Smashing Magazine。在那里我偶然发现了Heydon Pickering,后来他发明了“猫头鹰选择器”。这个选择器让我大吃一惊。在CSS Day上,他甚至展示了另一个绝妙的方案,名为“弹性盒子圣信天翁” (你可以在这里观看)。这些解决方案让我看到了 CSS 远比我想象的要强大得多。用 CSS 解决问题可以很简单,也可以很优雅。所以,Heydon,这个方案(部分)适合你。

“用 CSS 解决问题可以很简单,也可以很优雅。”

猫头鹰选择器

Heydon 在他的文章中比我更好地解释了选择器。但我将提供一个简短的摘要。如前所述,选择器非常简单:* + **是 CSS 中的通用选择器,它适用于 DOM 中的所有元素。+是这段 CSS 代码的真正英雄。它有一个美丽的名字“相邻兄弟组合器” 。如果紧跟在第一个元素之后,它会将定义的样式应用于第二个元素。使用我们的选择器,它会将样式应用于 DOM 中同一级别的所有非第一个元素。除非其他规则具有更高的特异性

* + * { 
  margin-top: 1.5rem;
}
Enter fullscreen mode Exit fullscreen mode

那么,为什么这个选择器如此强大呢?我在为博客网站搜索间距解决方案时发现了这个选择器。我希望在段落之间以及与其父元素之间留出足够的空间。有很多解决方案可以解决这个问题。你可以给每个元素一个margin-bottom。但这会对最后一个元素产生副作用。要解决这个问题,可以使用:last-of-type伪选择器覆盖样式。另一个解决方案是为每个段落添加 和padding-toppadding-bottom这可能会对父元素的填充产生不必要的副作用。关于这个具体问题和解决方案的更深入的描述可以在Every Layout上找到,这是一个非常棒的网站。

使用 时,这* + *似乎是一个优雅的解决方案margin-topmargin-top仅适用于元素之间。但您也可以使用间距不同的img或元素来放置图像。那么试试类似 的方法吧。您可以根据需要设置此结构,确保解决方案简洁优雅。但您知道是什么让它如此强大吗?在这种设置下,它适用于嵌套元素!svgp + p

猫头鹰选择器的应用

如左侧所示,该margin-top规则适用于列表中的每个元素。右侧的第二个元素不仅获得了样式规则,而且还有两个子元素。在这些子元素中,第二个元素还获得了外边距。

UI中的算法

你可能想知道我为什么对这个小小的 CSS 选择器如此着迷?在 Web 开发行业以及 2019 年的 CSS Days 大会上,有一个反复出现的话题:CSS 是一种编程语言吗?要回答这个问题,我推荐Lara Schenck的演讲,你可以在这里找到。这次演讲让我思考了 CSS 的现状以及我自己是如何使用它的。浏览器将我的 CSS 命令转换成屏幕上显示的内容有多复杂?

OWL 选择器是一个很好的例子,它展示了解析 CSS 选择器可以多么复杂。它展现了 CSS 的真正威力。为什么?因为它可以嵌套工作。在任何编程语言中,嵌套都会很快变得复杂。选择器的嵌套特性可以与递归相媲美。在深入研究递归解决方案之前,让我们先看一下扁平元素列表的伪代码。

免责声明:所使用的代码示例不适用于任何编程语言,它们是伪代码片段。

function owl(list, apply) {
  for (i = 1; i < list.length; i++) {
    apply(list[i]);
  }
}
Enter fullscreen mode Exit fullscreen mode

我们的函数接收一个元素列表和一个apply函数作为输入。如你所见,我们从 开始1。这并不是因为数组从 1 开始(它们不是),而是因为我们的 CSS 选择器默认跳过了第一个元素。该函数的工作方式类似于* + *非嵌套列表。回调函数apply可用于列表中的任何元素。但是,如果我们想要像p + p或 甚至 这样的值呢img + p?我们必须添加检查以确保相邻元素遵循定义。

function isFirst(item) { ... }
function isSecond(item) { ... }

function owl(list, apply) {
  for (i = 1; i < list.length; i++) {
      if (isFirst(list[i]) && isSecond(list[i - 1)) {
        apply(list[i]);
      }
  }
}
Enter fullscreen mode Exit fullscreen mode

使用该函数,我们可以处理不同类型的元素列表,并检查相邻元素是否符合我们的标准。我们唯一缺少的是 CSS 选择器的嵌套功能。我们可以检查元素是否有子元素。如果有,则owl再次调用该函数。然而,在 HTML 中,任何元素都可以有子元素。这意味着即使符合相邻规则的元素也可能有子元素。因此,我们不必在元素有子元素时才调用该owl函数,而是必须先调用该apply函数。因此,对于单个元素,可能会同时调用apply和。owl

function isFirst(item) { ... }
function isSecond(item) { ... }
function hasChildren(item) { ... }

function owl(list, apply) {
  for (i = 1; i < list.length; i++) {
    if (isFirst(list[i]) && isSecond(list[i - 1)) {
      apply(list[i]);
    }
    if (hasChildren(list[i]) {
        owl(list[i], apply);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

现在你已经知道如何创建类似猫头鹰选择器的东西了。它包含一个递归函数,并带有一些额外的函数来检查条件。如果我们的 CSS 变得更复杂,上面的伪代码也会变得更加复杂。尝试将它与不同的伪选择器组合使用,或者改变它的特异性。通过这样做,你会看到 CSS 变得多么强大。

CSS 是一种编程语言吗?

正如我提到的,在 CSS Days 期间,一个反复出现的话题是“CSS 是一种编程语言吗?”。几乎每个人都能运用简单的 CSS 规则或样式规则。解决更复杂(甚至简单)的问题需要更深入的知识。了解计算机科学概念变得非常重要,因为它们是 CSS 规则的结果。

“了解计算机科学概念非常重要,因为它们是 CSS 规则的结果。”
Kevin Pennekamp

一个简单的 CSS 选择器可能意味着你应用了一个递归函数。这无非就是使用别人编写的函数。结果的心理模型保持不变。你正在应用算法来创建 UI。这正是我热爱 CSS 的原因。一些简单的东西可以成为强大的 UI 操作工具。每当我在 CSS 中发现我认为不可能的解决方案时,我都会感激不尽。因此,我要感谢 Heydon Pickering、Lara Schenck 以及所有向我展示了 CSS 真正力量的人。

本文最初发布于kevtiq.co

鏂囩珷鏉ユ簮锛�https://dev.to/vyckes/an-ode-to-the-css-owl-selector-359c
PREV
揭开状态管理的神秘面纱
NEXT
理解 React 项目中的 Vite 流程和结构