配了三年动画,今天才发现 View Transitions 从来不是给整个页面准备的——element.startViewTransition() 把这件事彻底变了

写播放器的人都知道 View Transitions 好用,但一用就发现整个页面都被它接管了——你想给一个小按钮加展开动画,整个页面的 z-index 和 fixed 元素全乱了。

Chrome 147 发布了一个被严重低估的 API:element.startViewTransition()。它把视图转换从全页面锁变成了元素级工具——一个页面可以同时跑多个独立的过渡动画,互不干扰,页面其余部分保持交互。这是 View Transitions 走向组件化的关键一步。

为什么 View Transitions 一直用着别扭

原始的 document.startViewTransition() 是在整个文档级别做的。当它运行时:

  • 整个页面进入快照状态,视觉上暂停
  • 所有 fixed/absolute 定位元素的层叠上下文可能被打乱
  • 如果页面上有多个组件需要独立动画,只能排队等,或者干脆放弃

element.startViewTransition():把转换作用域收窄到一棵 DOM 子树

Chrome 147 把 startViewTransition() 从 Document 接口下放到了 Element 接口:

“`javascript
// 之前:整个文档级别的转换
document.startViewTransition(() => updateDOM());

// 现在:只影响这个元素及其子树
element.startViewTransition(() => updateDOM());
“`

作用域的变化带来三个本质改变:

一是可以真正并发。 页面上的列表、侧边栏、弹窗各自调用自己的 element.startViewTransition(),三个动画同时跑,不排队,不互相等待。

二是天然嵌套。 一个 element.startViewTransition() 内部可以再调用另一个子元素的转换,形成父子嵌套关系,浏览器自动处理层叠和包含关系。

三是 fixed 元素不再打架。 因为作用域被限定在子树内,父级页面的 fixed 元素不受影响。

“`javascript
// 两个列表各自独立动画,互不干扰
listA.startViewTransition(() => shuffleListA());
listB.startViewTransition(() => shuffleListB());
// 弹窗自己管自己的过渡,同时进行
modal.startViewTransition(() => showModal());
“`

实战:三步实现可并发、可嵌套的动画系统

第一步:基础并发转换

用 Chrome 官方 Demo「Shuffling Lists」的模式,两个列表同时打乱,互不等待:

“`javascript
function shuffle(listEl) {
listEl.startViewTransition(() => {
const items = Array.from(listEl.children);
items.sort(() => Math.random() – 0.5);
items.forEach(item => listEl.appendChild(item));
});
}

btn.addEventListener(“click”, () => {
shuffle(listA);
shuffle(listB); // 这个不会等 listA 跑完
});
“`

第二步:嵌套——列表项加入侧边栏

「我的巴塔哥尼亚之旅」Demo 展示了一个三层嵌套场景:网格筛选、商品卡片飞入、侧边栏展开同时进行:

“`javascript
grid.startViewTransition(() => filterGrid(category));
item.startViewTransition(() => moveToSidebar(item));
sidebar.startViewTransition(() => openSidebar());
“`

第三步:查询活跃的转换

Chrome 147 引入了两个新 API 来查询运行中的转换:

“`javascript
// ViewTransition.transitionRoot:返回这个转换的作用域根元素
const root = viewTransition.transitionRoot;

// Element.activeViewTransition:返回元素上当前正在运行的 ViewTransition 实例
const active = someElement.activeViewTransition;
“`

第四步:CSS 伪类控制

作用域根会自动应用 contain: layout 和 view-transition-scope: all,浏览器自动隔离处理,不再干扰父级 fixed 元素:

“`css
.my-widget {
view-transition-name: my-widget;
}
::view-transition-old(my-widget) {
animation: fade-out 200ms ease-out;
}
::view-transition-new(my-widget) {
animation: fade-in 200ms ease-in;
}
“`

:active-view-transition-type():给并发动画加类型标识

CSS View Transitions Level 2 引入了新伪类,给特定类型的转换打标签:

“`javascript
element.startViewTransition(() => updateSomething(), { type: “sidebar” });
“`

“`css
:is(:active-view-transition-type(sidebar), :active-view-transition-type(navigation)) {
animation-duration: 300ms;
}
“`

浏览器支持:现状和策略

浏览器 支持情况
Chrome 147+ 完整支持
Edge 147+ 完整支持
Firefox 无信号
Safari 无信号

建议用 feature detection 渐进增强:

“`javascript
if (HTMLElement.prototype.startViewTransition) {
// 使用 element.startViewTransition
} else {
// 回退到 document.startViewTransition 或不用动画
}
“`

和 MPA View Transitions 的关系

很多人会把 element-scoped 和跨页 MPA 跳转搞混。MPA 跳转用 pageswap / pagereveal 事件监听链接跳转,全程由浏览器控制快照;element-scoped 是在 SPA 场景下由 JS 调用 API 主动触发,作用域限定在元素子树内。两者不是替代关系,而是互补——MPA 处理页面间,element-scoped 处理页面内的组件级动画。

下一步

对于已经在用 View Transitions 的项目,element-scoped 是增量的、向后兼容的改进:

  • 现有 document.startViewTransition() 调用不受影响
  • 可以逐步把需要独立动画的组件改成 element.startViewTransition()
  • 嵌套层级控制在两层以内,避免复杂度失控

配了三年动画,今天才发现 View Transitions 从来不是一个全页锁,而是一套可以拆成原子粒度的工具。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 12 阅读