你以为 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 做更精细的控制。

落地方下一步

  1. 找项目里用 document.startViewTransition() 的地方,评估是否改为 element 级
  2. 对 Tab、Modal、列表重排等局部组件场景,优先用 element-scoped 方案
  3. 跑一下 caniuse,确认目标用户的 Chrome 覆盖率是否满足(Chrome 126+,目前 Chromium 系均已覆盖)

Chrome 从 126 到 154,视图过渡这套 API 正在从「整站大动画」变成「组件级精密控制」。整页有整页的用法,局部有局部的优势——现在两个粒度都有了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 14 阅读