你以为 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 合规的基本要求。
下一步:
- 打开 DevTools,试一个列表页,加
view-transition-class="item"看看效果 - 把手写
:root::view-transition-group(card-N)全部替换成::view-transition-group(*.card)通配写法 - 有 300+ 列表项的页面,审计
sibling-index()是否可用,逐步迁移
评论区
登录后可评论。