写过 CSS 的人都踩过这个坑——动画要 stagger、宽度要均分、颜色要平铺,以前全靠 JS 算 index 注入 custom property。今天两个 CSS 函数把这件事彻底原生化了

写过 CSS 的人都踩过这个坑——动画要 stagger、宽度要均分、颜色要平铺,以前全靠 JS 算 index 注入 custom property。今天两个 CSS 函数把这件事彻底原生化了


以前怎么做的

动画 stagger:5 个卡片错开 80ms 入场,传统做法:

items.forEach((item, i) => {
  item.style.setProperty(--delay, `${i * 80}ms`);
});
.card { animation-delay: var(--delay, 0ms); }

等宽 Grid:不知道有几个 tab,全靠后端渲染或者 JS 探测 children 数量再动态算宽度。

色彩平铺:N 张卡片从左到右色相均匀分布,以前要么硬编码要么 JS 循环注入 hsl 值。

根本问题是:浏览器知道这些信息,但 CSS 拿不到。:nth-child 只是个选择器,它能选中元素,但它的值不能参与 calc() 运算。


两个新函数

Chrome 138+ / Safari 26.2+ / Edge 138+ 稳定支持:

sibling-index() — 元素在其父级 children 中的位置(从 1 开始)

sibling-count() — 父级共有多少个 element children

两者都是纯数值,可以直接塞进 calc():

/* 动画 stagger,一行搞定 */
.card {
  animation: fade-in 0.4s ease both;
  animation-delay: calc(sibling-index() * 80ms);
}

/* 等宽 Grid,插一个动态刷新一个 */
.tab {
  width: calc(100% / sibling-count());
}

/* 色相均匀分布 */
.swatch {
  background: hsl(calc(sibling-index() * 360deg / sibling-count()) 70% 50%);
}

关键点:DOM 变了,CSS 自动重新计算。插一个卡片进去,所有卡片的宽度、延迟、色相全部自动刷新,零 JS 干预。

sibling-index() 从 1 开始计数,不算 text node 和 comment。hidden 元素(display: none)是否计入各浏览器略有差异,写关键线上样式时建议实测。


实战场景

1. 交错入场动画

正向 stagger(先出现的后延迟):

.card {
  animation: slide-up 0.4s ease both;
  animation-delay: calc(sibling-index() * 80ms);
}

反向 stagger(最后出现的先入场):

.card {
  animation: fade-in 0.4s ease both;
  animation-delay: calc((sibling-count() - sibling-index()) * 80ms);
}

配合 prefers-reduced-motion 关闭:

@media (prefers-reduced-motion: reduce) {
  .card { animation: none; }
}

2. 动态等宽 Grid

.grid { display: flex; flex-wrap: wrap; }
.col { flex: 1; min-width: calc(100% / 12); }

三个子元素各占 33.33%,四个各占 25%,删掉一个剩下三个自动重新分布——这个以前必须靠 JS resize observer 才能做到。

3. 色相平铺

.swatch {
  background: hsl(
    calc((360deg / sibling-count()) * sibling-index())
    70%
    50%
  );
}

N 张卡片从左到右 Hue 均分,第一次实现纯 CSS 自动跟随数量变化。

4. 径向布局

结合 CSS 三角函数做圆形排列:

.radial-item {
  --angle: calc((360deg / sibling-count()) * sibling-index());
  left: calc(50% + 100px * cos(var(--angle)));
  top: calc(50% + 100px * sin(var(--angle)));
}

避坑指南

1. Shadow DOM 边界

sibling-index() 和 sibling-count() 只看 immediate parent 的 direct children。Shadow DOM 内的元素只会计数 shadow tree 内的 sibling,不会穿透 ::slotted 看到 light DOM。

2. 不是替代 nth-child

:nth-child 是 selector,用来选元素;sibling-index() 是 value function,用来参与运算。用错场景会导致整条规则失效。

3. 支持检测

@supports (width: sibling-count()) {
  .col { width: calc(100% / sibling-count()); }
}

Firefox 目前还不支持,需要渐进增强。

4. 伪元素不参与计数

::before / ::after 不算 sibling 节点,但可以在伪元素里用 sibling-index()——此时以 originating element 为基准计算。


下一步

去 caniuse 查一下当前覆盖率,如果你的产品 Firefox 用户占比不高,直接用就行。典型适用场景:列表动画、动态 Grid、色相/透明度平铺序列。插一个元素自动刷新分布这种事,以前要写一整块 JS,现在两行 CSS 搞定。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 12 阅读