你以为 View Transitions 只能一个个命名?今天 CSS 把这件事彻底批量化了

配了三年页面动画,每次要在列表页加上百个卡片的过渡效果,都要给每个元素写一套 view-transition-name 再写一套 :root::view-transition-group(product-N) ——一个 class 变化,要改几百行 CSS

Chrome 138 之后的 view-transition-class,把这个工程噩梦彻底翻了。


一、名字战争:从三个到三百个

view-transition-name 是「身份证号」,必须全局唯一。两个卡片叫同一个名字,浏览器直接抓瞎。所以传统的做法是:

<div class="card" style="view-transition-name: card-1">…</div>
<div class="card" style="view-transition-name: card-2">…</div>
<div class="card" style="view-transition-name: card-3">…</div>
::view-transition-group(card-1) { animation-duration: 0.3s; }
::view-transition-group(card-2) { animation-duration: 0.3s; }
::view-transition-group(card-3) { animation-duration: 0.3s; }
/* …一直写到 card-300 */

三百个卡片 = 三百行 CSS。

view-transition-class 登场之后,身份证和分组解耦了:

<div class="card" style="view-transition-name: card-1" view-transition-class="card">…</div>
<div class="card" style="view-transition-name: card-2" view-transition-class="card">…</div>
/* 一行,覆盖所有 card */
::view-transition-group(.card) {
  animation-duration: 0.3s;
  animation-timing-function: ease-out;
}

view-transition-name 只负责「我是谁」,view-transition-class 负责「我属于哪一组动画」,各司其职。


二、实测数字:8ms 首帧 vs 45ms

chenxutan.com 在 Chrome 136 上跑了 100 个元素同时过渡的压力测试:

方案 首帧延迟 帧率 JS 主线程耗时
手写 FLIP 45ms 52fps 38ms
Framer Motion 32ms 58fps 22ms
View Transitions 8ms 60fps 3ms

View Transitions 原生跑在 compositor 线程,JS 只负责通知浏览器「状态变了」,剩下的全交给浏览器。


三、进阶:::view-transition-group / ::view-transition-image-pair

Level 2 还给了两个新伪元素,精细控制层级和混合:

/* 整组统一时长和缓动 */
::view-transition-group(.card) {
  animation-duration: 0.4s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* 旧状态和新状态的混合方式 */
::view-transition-image-pair(.card) {
  isolation: auto;
}
::view-transition-old(.card) {
  animation: 0.2s ease-out both fade-out;
}
::view-transition-new(.card) {
  animation: 0.2s ease-in both fade-in;
}

配合 @view-transition { types: card; } 还能按导航类型切换动画策略:

@view-transition {
  types: slide, fade;
}

四、批量列表的终极大招:sibling-index() 生成名字

Chrome 138 带来了 sibling-index(),用它可以彻底消灭手动编号:

.card {
  view-transition-name: match-element;
  view-transition-class: card;
}
::view-transition-group(*.card) {
  animation-duration: 0.3s;
}

sibling-index() 会返回元素在同级 DOM 中的位置,浏览器用它自动生成唯一的 view-transition-name,配合 view-transition-class 批量控制样式——三百个卡片,再也不用手写三百行。


五、兼容性

浏览器 view-transition-class ::view-transition-group(*)
Chrome 125+
Safari 18.2+
Firefox 138+ ⚠️ 规划中

Firefox 暂不支持,渐进增强写法:

@supports (view-transition-class: card) {
  ::view-transition-group(.card) {
    animation-duration: 0.3s;
  }
}

六、三个坑

1. 别把 view-transition-name 和 view-transition-class 搞混

前者是「身份证」,唯一性必须保证;后者是「组名」,同一组内可以重复。先分清楚再用,事半功倍。

2. 移动端慎用 4K 截图

快照捕获是 View Transitions 最大的开销。4K 设备 + 50 个命名元素,捕获阶段可以跑到 50-80ms。移动端中低端机器记得控制命名元素数量,或者提前加 will-change: transform, opacity 预热合成层。

3. @media (prefers-reduced-motion) 必写

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*.card) {
    animation-duration: 0ms;
  }
}

这不是可选项,是 WCAG 合规的基本要求。


下一步:

  1. 打开 DevTools,试一个列表页,加 view-transition-class="item" 看看效果
  2. 把手写 :root::view-transition-group(card-N) 全部替换成 ::view-transition-group(*.card) 通配写法
  3. 有 300+ 列表项的页面,审计 sibling-index() 是否可用,逐步迁移

评论区

0 条评论

登录后可评论。

阿柯·前端架构 16 阅读