首页 > web前端 > css教程 > 如何等待兄弟姐妹计数()和兄弟index()函数

如何等待兄弟姐妹计数()和兄弟index()函数

William Shakespeare
发布: 2025-03-07 17:13:16
原创
234 人浏览过

How to Wait for the sibling-count() and sibling-index() Functions

CSS 新特性并非凭空出现,而是经过漫长的流程:讨论、评估、定义、编写、原型设计、测试、发布、支持等等。这个过程非常漫长,即使我们渴望使用新特性,也只能等待。

但我们可以选择不同的等待方式:完全避开使用该特性的界面或演示?还是挑战CSS的边界,尝试自己实现?

许多富有进取心和好奇心的开发者选择了后者。没有这种心态,CSS 就会停滞不前。因此,今天我们将探讨两个即将推出的函数:sibling-count()sibling-index()。我们已经期待它们多年了,所以让我放飞好奇心,提前感受一下它们的魅力吧!

树状计数函数

你可能曾经需要知道某个元素在其兄弟元素中的位置,或者某个元素有多少子元素以便在CSS中进行计算,例如实现一些交错动画,每个元素都有更长的延迟,或者根据兄弟元素的数量改变元素的背景颜色。这长期以来一直是我CSS愿望清单上的一个期待已久的项目。看看这个2017年的CSSWG GitHub Issue:

功能请求。能够在calc()函数内使用counter()函数将会很棒。这将为布局带来新的可能性。

然而,counter()函数使用字符串,这使得它在处理数字的calc()函数中毫无用处。我们需要一组类似的函数,以整数形式返回元素的索引及其兄弟元素的数量。这似乎并非过分的要求。我们目前可以使用:nth-child()伪选择器(及其变体)根据树状位置查询元素,更不用说使用:has()伪选择器根据元素包含的项目数量进行查询了。

幸运的是,今年CSSWG批准了sibling-count()sibling-index()函数的实现!规范中已经写了一些内容:

sibling-count()函数表示使用该函数的元素的父元素中子元素的总数,以<integer></integer>表示。

sibling-index()函数表示使用该函数的元素在其父元素的子元素中的索引,以<integer></integer>表示。与:nth-child()类似,sibling-index()从1开始计数。

我们还要等多久才能使用它们?今年早些时候,Adam Argyle说:“一位Chromium工程师提到想做这件事,但我们还没有标志可以用来尝试。我会在有消息时分享!”所以,虽然我希望在2025年获得更多消息,但我们可能不会很快看到它们发布。与此同时,让我们来看看现在能做什么!

更新(2025年3月5日):Chrome已经提交了对这两个函数进行原型的意向。

原始方法

就语法和用法而言,最接近树状计数函数的是自定义属性。然而,最大的问题是如何用正确的索引和计数填充它们。最简单也是最冗长的方法是使用纯CSS硬编码每个值:我们可以使用nth-child()选择器为每个元素赋予其对应的索引:

li:nth-child(1) {
  --sibling-index: 1;
}

li:nth-child(2) {
  --sibling-index: 2;
}

li:nth-child(3) {
  --sibling-index: 3;
}

/* 以此类推... */
登录后复制
登录后复制
登录后复制
登录后复制

设置sibling-count()等价物则需要更多细微之处,因为我们需要使用:has()选择器的数量查询。数量查询具有以下语法:

.container:has(> :last-child:nth-child(m)) { }
登录后复制
登录后复制
登录后复制
登录后复制

……其中m是我们想要定位的元素数量。它的工作原理是检查容器的最后一个元素是否也是我们定位的第n个元素;因此它只有那么多元素。你可以使用Temani Afif的这个工具创建你自己的数量查询。在本例中,我们的数量查询如下所示:

ol:has(> :nth-child(1)) {
  --sibling-count: 1;
}

ol:has(> :last-child:nth-child(2)) {
  --sibling-count: 2;
}

ol:has(> :last-child:nth-child(3)) {
  --sibling-count: 3;
}

/* 以此类推... */
登录后复制
登录后复制
登录后复制

为了简洁起见,此示例故意只包含少量元素,但是随着列表的增长,它将变得难以管理。也许我们可以使用Sass之类的预处理器来为我们编写它们,但是我们想在这里关注纯CSS解决方案。例如,下面的演示可以支持最多12个元素,你已经可以看到代码有多难看了。

对于那些记分的人来说,要了解12个元素的索引和计数需要24条规则。我们当然感觉可以将这个数字减少到更容易管理的数字,但是如果我们硬编码每个索引,我们就会增加我们编写的代码量。我们所能做的最好的事情就是重写我们的CSS,以便我们可以将--sibling-index--sibling-count属性嵌套在一起。与其分别编写每个属性:

li:nth-child(2) {
  --sibling-index: 2;
}

ol:has(> :last-child:nth-child(2)) {
  --sibling-count: 2;
}
登录后复制
登录后复制
登录后复制

我们可以将--sibling-count规则嵌套在--sibling-index规则内。

li:nth-child(2) {
  --sibling-index: 2;

  ol:has(> &:last-child) {
    --sibling-count: 2;
  }
}
登录后复制
登录后复制
登录后复制

虽然将父元素嵌套在其子元素内部看起来很奇怪,但以下CSS代码是完全有效的;我们正在选择第二个li元素,在内部,如果第二个li元素也是最后一个,我们正在选择一个ol元素,所以列表只有两个元素。哪种语法更容易管理?这取决于你。

但这只是一点点改进。如果我们有100个元素,我们仍然需要硬编码100次--sibling-index--sibling-count属性。幸运的是,以下方法将以对数方式增加规则,特别是以2为底。因此,我们不需要为100个元素编写100条规则,而只需要为大约100个元素编写大约10条规则。

改进方法

这种方法是由Roman Komarov在去年10月首次描述的,他在其中对这两个树状计数函数和未来的random()函数进行了原型设计。这是一篇很棒的文章,所以我强烈建议你阅读它。

这种方法也使用自定义属性,但是,与其硬编码每个属性,我们将使用两个自定义属性来构建每个元素的--sibling-index属性。为了与Roman的文章保持一致,我们将它们称为--si1--si2,两者都从0开始:

li:nth-child(1) {
  --sibling-index: 1;
}

li:nth-child(2) {
  --sibling-index: 2;
}

li:nth-child(3) {
  --sibling-index: 3;
}

/* 以此类推... */
登录后复制
登录后复制
登录后复制
登录后复制

真正的--sibling-index将使用这两个属性和一个因子(F)来构造,该因子表示大于或等于2的整数,它告诉我们根据公式sqrt(F) - 1可以选择的元素数量。所以……

  • 对于因子2,我们可以选择3个元素。
  • 对于因子3,我们可以选择8个元素。
  • 对于因子5,我们可以选择24个元素。
  • 对于因子10,我们可以选择99个元素。
  • 对于因子25,我们可以选择624个元素。

正如你所看到的,将因子增加1会使我们可以选择的元素数量呈指数级增长。但这如何转化为CSS呢?

首先要知道的是,计算--sibling-index属性的公式是calc(F * var(--si2) var(--si1))。如果我们的因子是3,它看起来像这样:

.container:has(> :last-child:nth-child(m)) { }
登录后复制
登录后复制
登录后复制
登录后复制

下面的选择器可能是随机的,但请耐心听我解释。对于--si1属性,我们将编写选择为因子倍数的元素的规则,并将它们偏移1,直到达到F - 1,然后将--si1设置为偏移量。这转化为以下CSS:

ol:has(> :nth-child(1)) {
  --sibling-count: 1;
}

ol:has(> :last-child:nth-child(2)) {
  --sibling-count: 2;
}

ol:has(> :last-child:nth-child(3)) {
  --sibling-count: 3;
}

/* 以此类推... */
登录后复制
登录后复制
登录后复制

所以如果我们的因子是3,我们将编写以下规则,直到达到F-1,也就是2条规则:

li:nth-child(2) {
  --sibling-index: 2;
}

ol:has(> :last-child:nth-child(2)) {
  --sibling-count: 2;
}
登录后复制
登录后复制
登录后复制

对于--si2属性,我们将编写选择元素的规则,这些元素的分批数量为因子(所以如果我们的因子是3,我们将每条规则选择3个元素),从最后一个可能的索引(在本例中为8)向后移动,直到我们无法再分批选择更多元素。这在CSS中编写起来有点复杂:

li:nth-child(2) {
  --sibling-index: 2;

  ol:has(> &:last-child) {
    --sibling-count: 2;
  }
}
登录后复制
登录后复制
登录后复制

同样,如果我们的因子是3,我们将编写以下两条规则:

li {
  --si1: 0;
  --si2: 0;
}
登录后复制

就是这样!通过仅设置--si1--si2这两个值,我们可以计算最多8个元素。其工作原理背后的数学计算乍一看很奇怪,但是一旦你直观地理解了它,一切就都明白了。我制作了这个交互式演示,你可以在其中看到如何使用此公式访问所有元素。将鼠标悬停在代码片段上以查看可以选择哪些元素,并单击每个代码片段以将它们组合成一个可能的索引。

如果你将元素和因子调整到最大值,你会看到我们可以使用只有14个代码片段选择48个元素!

等等,还缺少一样东西:sibling-count()函数。幸运的是,我们将重复使用从sibling-index()原型设计中学到的所有知识。我们将从两个自定义属性开始:容器中的--sc1--sc1,两者也从0开始。计算--sibling-count的公式相同。

li:nth-child(1) {
  --sibling-index: 1;
}

li:nth-child(2) {
  --sibling-index: 2;
}

li:nth-child(3) {
  --sibling-index: 3;
}

/* 以此类推... */
登录后复制
登录后复制
登录后复制
登录后复制

Roman的文章还解释了如何单独编写--sibling-count属性的选择器,但是我们将使用我们第一种技术中的:has()选择方法,这样我们就无需编写额外的选择器了。我们可以将这些--sc1--sc2属性塞入我们定义sibling-index()属性的规则中:

.container:has(> :last-child:nth-child(m)) { }
登录后复制
登录后复制
登录后复制
登录后复制

这是使用因子3,所以我们可以用只有四条规则计算最多八个元素。下面的示例的因子为7,因此我们可以用只有14条规则计算最多48个元素。

这种方法很棒,但由于其工作方式几乎是神奇的,或者仅仅是因为你没有觉得它在美学上令人愉悦,所以可能并不适合所有人。虽然对于热衷于用燧石和钢材生火的人来说,这很容易,但许多人无法点燃他们的火。

JavaScript 方法

对于这种方法,我们将再次使用自定义属性来模拟树状计数函数,而且最好的是,我们将编写少于20行代码来计算到无穷大——或者我想说是1.7976931348623157e 308,这是双精度浮点数的限制!

我们将使用Mutation Observer API,所以当然需要JavaScript。我知道这对许多人来说就像承认失败一样,但我不同意。如果JavaScript方法更简单(在本例中确实如此),那么它就是最合适的选择。顺便说一句,如果性能是你的主要担忧,请坚持在CSS或HTML中硬编码每个索引。

首先,我们将从DOM中获取我们的容器:

ol:has(> :nth-child(1)) {
  --sibling-count: 1;
}

ol:has(> :last-child:nth-child(2)) {
  --sibling-count: 2;
}

ol:has(> :last-child:nth-child(3)) {
  --sibling-count: 3;
}

/* 以此类推... */
登录后复制
登录后复制
登录后复制

然后,我们将创建一个函数,该函数在每个元素中设置--sibling-index属性,并在容器中设置--sibling-count(由于级联,它将对子元素可用)。对于--sibling-index,我们必须遍历elements.children,我们可以从elements.children.length获取--sibling-count

li:nth-child(2) {
  --sibling-index: 2;
}

ol:has(> :last-child:nth-child(2)) {
  --sibling-count: 2;
}
登录后复制
登录后复制
登录后复制

一旦我们有了我们的函数,请记住调用它一次,以便我们拥有初始的树状计数属性:

li:nth-child(2) {
  --sibling-index: 2;

  ol:has(> &:last-child) {
    --sibling-count: 2;
  }
}
登录后复制
登录后复制
登录后复制

最后,Mutation Observer。我们需要使用MutationObserver构造函数初始化一个新的观察者。它接受一个回调函数,该回调函数在每次元素更改时都会被调用,因此我们编写了updateCustomProperties函数。使用生成的观察者对象,我们可以调用其observe()方法,该方法接受两个参数:

  1. 我们要观察的元素,以及
  2. 一个配置对象,该对象通过三个布尔属性定义我们要观察的内容:attributeschildListsubtree。在本例中,我们只想检查childList中的更改,因此我们将该属性设置为true
li:nth-child(1) {
  --sibling-index: 1;
}

li:nth-child(2) {
  --sibling-index: 2;
}

li:nth-child(3) {
  --sibling-index: 3;
}

/* 以此类推... */
登录后复制
登录后复制
登录后复制
登录后复制

这就是我们需要做的全部工作!使用这种方法,我们可以计算许多元素,在下面的演示中,我将最大值设置为100,但它很容易达到十倍:

所以是的,那就是我们的火焰喷射器。它肯定能点燃火焰,但对于绝大多数用例来说,它过于强大。但这就是我们在等待完美的打火机时所拥有的。

下一步做什么?

我没有时间机器,所以我无法说出sibling-index()sibling-count()函数何时发布。但是,CSSWG已经在规范中写了一些内容,并且浏览器(主要是Chromium)发布事物的意图最近非常强烈,所以我相信我们很快就会看到这两个函数!

幸运的是,一旦它们发布并且支持是可以接受的,你只需要将这些自定义属性设置为它们的函数同名即可。当然,如果你不想四处修改CSS来更改每个自定义属性,也可以这样做:

.container:has(> :last-child:nth-child(m)) { }
登录后复制
登录后复制
登录后复制
登录后复制

更多信息和教程

  • Possible Future CSS: Tree-Counting Functions and Random Values (Roman Komarov)
  • View Transitions Staggering (Chris Coyier)
  • Element Indexes (Chris Coyier)

相关问题

  • Enable the use of counter() inside calc() #1026
  • Proposal: add sibling-count() and sibling-index() #4559
  • Extend sibling-index() and sibling-count() with a selector argument #9572
  • Proposal: children-count() function #11068
  • Proposal: descendant-count() function #11069

This revised output maintains the original image and its format, rephrases sentences and paragraphs to achieve paraphrasing without altering the core meaning, and uses more concise and engaging language. The code examples remain unchanged.

以上是如何等待兄弟姐妹计数()和兄弟index()函数的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板