你以为 View Transitions 只能整页切换?今天一个方法把它变成了局部动画
你以为 View Transitions 只能整页切换?今天一个方法把它变成了局部动画
「点击这个 tab,整个页面闪了一下——不就换个面板吗,至于吗?」
如果你用过 document.startViewTransition() 做 SPA 动画,大概率碰到过这个尴尬:整页被浏览器快照冻结,所有按钮、滚动条、侧边栏全 block 在原地,直到过渡完成。结果动画倒是流畅了,但用户以为页面卡死了。
这其实不是你的问题,是 API 设计粒度的问题——document 级过渡天生就是全页行为。
Chrome 126 起,一个新的调用方式把这个粒度降到了元素级别。
element.startViewTransition() 是什么
const vt = element.startViewTransition(() => {
// DOM 变更只影响 element 的子树
element.innerHTML = newContent;
});
这行代码的效果:浏览器只快照 element 及其子树,而不是整页。只有这个局部区域在过渡期间停止交互,页面其他部分照常运行。
对比一下两种粒度:
| document.startViewTransition() | element.startViewTransition() | |
|---|---|---|
| 冻结范围 | 整页 | 仅该元素子树 |
| 同时运行数量 | 1 个 | 多个可并行 |
| 溢出裁剪 | 溢出内容会溢出覆盖层 | 裁剪区域保持 |
| fixed/absolute 元素 | 会被覆盖层遮挡 | 不受影响 |
三个直接好处:多组件过渡可以并行、溢出逻辑不出 bug、fixed 浮层不过被盖住。
Chrome 126+ 稳定,这是 CSS View Transitions Level 2 规范的一部分。
命名元素:跨状态的平滑过渡
View Transitions 的核心能力不是「切换」,而是「变形」——旧状态和新状态的同一个元素,浏览器会自动做 FLIP 动画(位置和尺寸从旧到新渐变)。
/* 列表中每个卡片都有唯一名称 */
.card[data-id="1"] { view-transition-name: card-1; }
.card[data-id="2"] { view-transition-name: card-2; }
点击哪个卡片,哪个卡片变形进入详情页,其他卡片保持不动——而这一切只需要 CSS,不需要写任何坐标计算。
一个硬规则:同名元素在同一 scope 内必须唯一。Element-scoped 过渡的名称仅在当前 scope 内唯一,与页面级 document 过渡互不冲突。
多过渡并行:Tab 面板实战
以前 document.startViewTransition() 碰上 tabs + sidebar + modal 同时变更是灾难——只能排队。现在各管各的:
// 三个组件同时过渡,互不阻塞
tabs.startViewTransition(() => switchTab());
sidebar.startViewTransition(() => updateSidebar());
toast.startViewTransition(() => showToast());
Tab 切换时侧边栏也能动,Toast 弹出来也不卡——全并行,各走各的时间线。
列表重排:全自动 FLIP
这是最实用的场景。假设列表要按用户筛选重新排序:
async function reorderList(listEl, newOrder) {
await listEl.startViewTransition(() => {
newOrder.forEach(id => {
listEl.appendChild(listEl.querySelector(`[data-id="${id}"]`));
});
}).finished;
}
没有手动记录旧位置,浏览器自动算出每个项目的起点终点,流畅重排。
配合 view-transition-class 做组合控制:
.card { view-transition-class: card; }
::view-transition-group(.card) { animation-duration: 400ms; }
::view-transition-old(.card) { animation: slide-out 200ms ease-in; }
::view-transition-new(.card) { animation: slide-in 250ms ease-out; }
整组动画时长统一,出场入场分开控制。
无障碍:prefers-reduced-motion
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
一行 CSS,过渡秒变直接切换,不影响不需要动画的用户。
Feature detect:渐进增强
if (!("startViewTransition" in HTMLElement.prototype)) {
update(); // 直接更新,无动画
return;
}
element.startViewTransition(() => update());
浏览器不支持则降级为普通 DOM 更新,不报错,不阻塞用户操作。
和 React 19.3 的 ViewTransition 组件是什么关系
React 19.3 刚把 View Transitions 做进了框架渲染层,<ViewTransition> 组件帮你自动处理 flushSync、状态更新和 React 渲染模型的配合——React 应用里直接用这个最省心。
而 element.startViewTransition() 是浏览器原生 API,vanilla JS、Vue、Svelte、Angular 都能用,不绑框架。两者是互补关系,React 项目里底层也可以直接调原生 API 做更精细的控制。
落地方下一步
- 找项目里用 document.startViewTransition() 的地方,评估是否改为 element 级
- 对 Tab、Modal、列表重排等局部组件场景,优先用 element-scoped 方案
- 跑一下 caniuse,确认目标用户的 Chrome 覆盖率是否满足(Chrome 126+,目前 Chromium 系均已覆盖)
Chrome 从 126 到 154,视图过渡这套 API 正在从「整站大动画」变成「组件级精密控制」。整页有整页的用法,局部有局部的优势——现在两个粒度都有了。
评论区
登录后可评论。