CSS View Transitions 新语法:sibling-index() + ident(),千级元素命名难题的终局方案

如果你写过 48 条 ::view-transition-group(card-N) 选择器------这篇文章就是来终结这种痛苦的。

一、引言:从"优雅的过渡"到"选择器地狱"

View Transitions API 让页面间的切换有了电影级的过渡效果,用一个 view-transition-name 就能让浏览器自动 morph 两个元素。教程里永远是同一个例子:一张英雄图片从列表页到详情页的过渡,一条 view-transition-name: hero 搞定。

但现实不是教程。

当你有一个 48 张卡片的商品列表页,每个卡片都需要独立的过渡动画时,你需要 48 个唯一的 view-transition-name,以及 48 组 ::view-transition-group(card-1)::view-transition-group(card-48) 的选择器。如果是 200 张呢?1000 张呢?

css 复制代码
/* 噩梦:每个元素一条规则 */
li:nth-child(1) { --idx: 1; }
li:nth-child(2) { --idx: 2; }
li:nth-child(3) { --idx: 3; }
/* ... 还有 45 条 ... */
li:nth-child(48) { --idx: 48; }

li {
  view-transition-name: var(--vt-card-\(--idx\));
}

::view-transition-group(card-1),
::view-transition-group(card-2),
::view-transition-group(card-3),
/* kill me */
::view-transition-group(card-48) {
  animation-duration: 0.35s;
}

这不是工程,这是体力活。更关键的是------你告诉了浏览器一件它本来就知道的事:哪个元素排第几个。浏览器构建了 DOM 树,它当然知道。只是 CSS 没法访问这个信息。

现在可以了。 CSS Values and Units Module Level 5 带来了 sibling-index()sibling-count(),加上即将到来的 ident() 函数,这个延续多年的痛点即将被彻底解决。


二、sibling-index():浏览器终于告诉你"排第几"

2.1 基本语法

sibling-index() 是 CSS 值函数,返回当前元素在兄弟节点中的 1-based 位置。无参数,直接用:

css 复制代码
li {
  animation-delay: calc(sibling-index() * 100ms);
}

第一个 <li> 返回 1,第二个返回 2,以此类推。只计元素节点,文本节点、注释、空白全部忽略。

2.2 和 :nth-child() 的本质区别

这是最容易混淆的地方:

维度 :nth-child() sibling-index()
类型 选择器 值函数
作用 选中第 N 个元素 返回 N 这个数字
能参与 calc()? ❌ 不能写 calc(:nth-child() * 10px) ✅ 直接算
响应 DOM 变化? 重新匹配选择器 重新计算样式值

核心差异::nth-child() 是"匹配器",决定规则应用到谁身上;sibling-index() 是"取值器",给你一个可以计算的数字。它们解决的是不同层面的问题。

2.3 sibling-count():知道"一共几个"

sibling-count() 返回父元素下子元素的总数,相当于 CSS 里的 element.parentElement.children.length

css 复制代码
/* 自动等分色相环 */
.tag {
  background-color: hsl(
    calc(360 / sibling-count() * (sibling-index() - 1)),
    70%,
    55%
  );
}

5 个标签产生 72° 间隔的颜色,10 个标签变成 36° 间隔。增删元素,分布自动重算。没有 Sass 循环,没有 JavaScript。

2.4 返回的是真·整数,不是字符串

sibling-index() 返回 <integer> 类型,不是 counter() 那种 <string>。这意味着你可以直接丢进 calc()min()max()round()mod()、甚至 sin()/cos()

css 复制代码
li {
  animation-delay: calc(sibling-index() * 100ms);     /* ✅ 时间值 */
  width: calc(sibling-index() * 50px);                 /* ✅ 长度值 */
  transform: rotate(calc(sibling-index() * 30deg));    /* ✅ 角度值 */
}

CSS 会自动处理类型转换,calc(sibling-index() * 100ms) 输出的是合法的 <time> 值。不需要任何技巧。


三、实战:sibling-index() 解决的五大痛点

3.1 交错动画------一行搞定

css 复制代码
/* 以前:N 条 nth-child 规则 */
li:nth-child(1) { --idx: 1; }
li:nth-child(2) { --idx: 2; }
/* ... */

/* 现在:一行 */
li {
  animation: fadeIn 0.4s ease both;
  animation-delay: calc((sibling-index() - 1) * 0.1s);
}

5 个元素还是 5000 个元素,这一行都能工作。增删列表项,延迟自动调整。

3.2 反向交错------从最后一个开始

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

最后一个元素 delay = 0ms 立刻触发,第一个元素延迟最长。用 sibling-count()sibling-index() 就行。

3.3 中间向两边扩散

css 复制代码
.card {
  --mid: calc((sibling-count() + 1) / 2);
  --dist: calc(abs(sibling-index() - var(--mid)));
  animation-delay: calc(var(--dist) * 60ms);
}

3.4 动态宽度分配

css 复制代码
/* 每个子元素比前一个宽 50px */
li {
  width: calc(50px + sibling-index() * 20px);
}

3.5 进度指示器

css 复制代码
.step {
  background: hsl(
    calc(120 / sibling-count() * sibling-index()),
    70%,
    50%
  );
}

从红色(0°)渐变到绿色(120°),步数自动适应。


四、ident():View Transitions 千级命名的钥匙

4.1 终极方案:一行代码生成千级唯一名称

sibling-index() 已经在 Chrome 137+ 上市了,但它只能给你一个数字。View Transitions 需要的是 <custom-ident>------像 card-1card-2 这样的标识符。你需要把数字"拼"成名字。

这就是 ident() 的作用。它是 Chrome 团队的 Bramus 向 CSSWG 提出的函数,接收字符串、整数或其他标识符,拼接后输出合法的 CSS 名称:

css 复制代码
/* 理想状态:ident() 上线后 */
.card {
  view-transition-name: ident("card-" sibling-index());
  view-transition-class: card;
}

一条规则,生成 card-1card-2card-3......1000 张卡片 = 1000 个唯一名称 = 一行 CSS。

4.2 不止 View Transitions

ident() 的价值远超视图过渡。任何需要动态生成唯一 CSS 标识符的场景都能用:

css 复制代码
/* 滚动时间线 */
.card {
  scroll-timeline-name: ident("tl-" sibling-index());
}

/* 容器查询 */
.widget {
  container-name: ident("widget-" attr(id));
}

/* 视图时间线 */
.section {
  view-timeline-name: ident("section-" sibling-index());
}

甚至可以和 attr() 组合,从 HTML 属性构造标识符:

css 复制代码
.card {
  view-transition-name: ident("item-" attr(data-id));
}

4.3 现实:ident() 还没上线

截至 2026 年 5 月,ident() 还没有在任何浏览器中实现。Chrome 在 2025 年 5 月提交了 Intent to Prototype,但"在雷达上"和"在你浏览器里"是两码事。

所以现在 sibling-index() + ident() 的组合还不能直接用。但了解这个方向很重要------一旦 ident() 上线,本文接下来要讲的所有"当前解决方案"的复杂度都会大幅降低。


五、当前可用的替代方案

ident() 到来之前,我们有几种方式处理千级元素的 View Transitions 命名。

5.1 view-transition-class:名称管身份,class 管样式

这是当前最实用的方案。核心思路是把"唯一性"和"统一样式"分离:

css 复制代码
/* 每个卡片有唯一名称(身份),共享同一个 class(样式钩子) */
.card {
  view-transition-class: card;
}
html 复制代码
<a href="/product/42" class="card"
   style="view-transition-name: product-42; view-transition-class: card">
  <img src="/images/42-thumb.jpg" alt="Widget" />
</a>
<a href="/product/43" class="card"
   style="view-transition-name: product-43; view-transition-class: card">
  <img src="/images/43-thumb.jpg" alt="Gadget" />
</a>

然后用通配符选择器 + class 匹配所有卡片:

css 复制代码
/* 一条规则搞定所有卡片的动画 */
::view-transition-group(*.card) {
  animation-duration: 0.35s;
  animation-timing-function: ease-out;
}

::view-transition-old(*.card),
::view-transition-new(*.card) {
  object-fit: cover;
}

*.card 的意思是"任何 view-transition-name + view-transition-class: card 的元素"。6 张卡片是 3 条规则,600 张卡片还是 3 条规则。

关键区分

  • view-transition-name = 主键,唯一标识"哪个元素跨页面对应",必须唯一
  • view-transition-class = 分类,回答"动画效果怎么统一",可以共享

5.2 用 attr() 从 HTML 属性取名称

Chrome 已经支持 attr() 解析为 <custom-ident>

html 复制代码
<div id="card-1" class="card">...</div>
<div id="card-2" class="card">...</div>
<div id="card-3" class="card">...</div>
css 复制代码
.card {
  view-transition-name: attr(id <custom-ident>);
  view-transition-class: card;
}

这样不用 JS 注入 inline style,从 HTML 的 id 属性直接生成 view-transition-name。但前提是你的 id 值要规划好。

5.3 JS 动态分配:pageswap / pagereveal

对于跨文档(MPA)的 View Transitions,用 pageswappagereveal 事件在导航瞬间动态分配名称:

js 复制代码
// 在离开页面上,给即将参与过渡的元素分配名称
document.addEventListener('pageswap', (event) => {
  const targetUrl = new URL(event.activation.entry.url);
  const targetId = targetUrl.searchParams.get('id');

  document.querySelectorAll('.card').forEach((card) => {
    if (card.dataset.id === targetId) {
      card.style.viewTransitionName = 'selected-card';
    }
  });
});

注意:浏览器分配给 pageswap 的窗口只有 10-50ms,逻辑要尽量轻量。

5.4 过渡结束后清理名称

js 复制代码
const transition = document.startViewTransition(() => {
  // 更新 DOM
});

transition.finished.then(() => {
  // 清理所有动态名称,避免下次冲突
  document.querySelectorAll('[style*="view-transition-name"]').forEach((el) => {
    el.style.viewTransitionName = '';
  });
});

六、sibling-index() 在 View Transitions 中的更多玩法

即使没有 ident()sibling-index() 也能在 View Transitions 的伪元素样式中发挥作用------通过交错动画让过渡更精致:

6.1 卡片网格交错入场

css 复制代码
.card {
  view-transition-class: card;
}

::view-transition-group(*.card) {
  animation-duration: 0.4s;
  animation-delay: calc(sibling-index() * 50ms);
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}

每个卡片的过渡依次延迟 50ms,形成优雅的瀑布效果。

6.2 图片画廊 morph

css 复制代码
.gallery-item {
  view-transition-class: gallery-item;
}

::view-transition-old(*.gallery-item),
::view-transition-new(*.gallery-item) {
  object-fit: cover;
  overflow: hidden;
}

::view-transition-group(*.gallery-item) {
  animation-duration: 0.5s;
  animation-delay: calc(sibling-index() * 30ms);
}

七、浏览器支持现状

特性 Chrome Edge Firefox Safari
sibling-index() 137+ ✅ 137+ ✅
sibling-count() 137+ ✅ 137+ ✅
ident() ❌ 未上线
view-transition-class 125+ ✅ 125+ ✅
attr(id <custom-ident>) 125+ ✅ 125+ ✅
Cross-doc View Transitions 125+ ✅ 125+ ✅

渐进增强策略sibling-index() 和 View Transitions 都是增强型特性------不支持的浏览器只是跳过动画,不影响功能。用 @supports 做条件加载即可:

css 复制代码
@supports (animation-delay: calc(1 * 1ms)) {
  /* sibling-index() 可用时使用 */
  li {
    animation-delay: calc((sibling-index() - 1) * 0.1s);
  }
}

别忘了配合 prefers-reduced-motion------这不是可选项:

css 复制代码
@media (prefers-reduced-motion: no-preference) {
  li {
    animation-delay: calc((sibling-index() - 1) * 0.1s);
  }
}

八、总结:从"告诉浏览器它已经知道的事"到"让浏览器自己算"

回顾一下,千级元素命名问题的本质是:你在用 CSS 告诉浏览器一件它本就知道的事------"这个元素排第几个"。之前 CSS 没法访问这个信息,所以我们写了无数 :nth-child() 规则、Sass 循环、JS 内联样式来弥补。

sibling-index() 打破了这层障碍,让 CSS 第一次能以"值"的形式获取元素的位置信息。而 ident() 一旦上线,view-transition-name: ident("card-" sibling-index()) 这一行代码就会彻底终结千级命名的噩梦。

当前阶段:

  • 交错动画sibling-index() 已经可用,Chrome 137+ 直接上
  • View Transitions 千级命名 :用 view-transition-class + attr(id) 作为过渡方案
  • 终极方案 :等 ident() 上线,一条规则搞定一切

浏览器已经知道你的元素排第几个了------现在,CSS 终于也能知道了。


参考来源


本文由AI辅助生成

相关推荐
daols881 小时前
vxe-table 进阶:同时使用 formatter 与 cell-render 实现格式化与样式定制
前端·javascript·vue.js·vxe-table
用户059540174461 小时前
用LangChain+FastAPI构建私有知识库踩坑实录:这3个问题让我排查了整整8小时
前端·css
前端张三1 小时前
ant design vue table 使用虚拟滚动
前端·javascript·vue.js
木子雨廷1 小时前
Flutter 内存管理实战:从 GC 原理到 DevTools 泄漏排查
前端·flutter
Rkgua1 小时前
TS中`Function`、`CallableFunction` 和 `NewableFunction`的函数区别
前端
Asize1 小时前
重生之我在 Vibe Coding 时代当程序员:第十一课,JS底层 :变量提升真相
前端·javascript
HYCS1 小时前
用pixi.js实现fabric.js(五):事件系统
前端·javascript·canvas
Momo__1 小时前
Node.js 26 来了:Temporal API 默认启用,Date 终于可以退休了
前端·node.js
雨季mo浅忆1 小时前
记录前端内网开发之新入职篇
前端·内网开发