关于 vercel-react-view-transitions
面向 React 应用开发者的技能指南,介绍如何使用浏览器原生 View Transitions 在 UI 状态间制作动画。内容涵盖共享元素、列表变化、Suspense 和路由切换,并强调动画适用场景、无障碍与浏览器降级。
主要功能
- 01原生 View Transitions 集成
- 02共享元素与列表动画
- 03Suspense 和路由切换模式
- 04按转换类型配置 CSS 动画
- 05无障碍与浏览器降级指引
适用场景
- 01为列表到详情页添加连续转场
- 02展示 Suspense 内容加载过程
- 03为列表重排设计身份动画
- 04审查现有应用的转场实现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ julianromli/ ai-skills --skill vercel-react-view-transitions安装后在 Claude Code 中输入/vercel-react-view-transitions确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdmetadata.jsonREADME.mdreferences/css-recipes.mdimplementation.mdnextjs.mdpatterns.md详细介绍
React View Transitions
使用浏览器原生的 document.startViewTransition 在 UI 状态之间添加动画。用 <ViewTransition> 声明是什么,用 startTransition / useDeferredValue / Suspense 触发何时,用 CSS 类控制如何。不支持的浏览器会平稳跳过动画。
何时添加动画
每个 <ViewTransition> 都应表达空间关系或连续性。如果你无法说明它表达了什么,就不要添加。
按以下顺序实现此列表中所有适用的模式:
| 优先级 | 模式 | 表达的含义 |
|---|---|---|
| 1 | 共享元素(name) | “同一事物——深入查看” |
| 2 | Suspense 显示 | “数据已加载” |
| 3 | 列表项身份(每项的 key) | “相同的项目,新的排列方式” |
| 4 | 状态变化(enter/exit) | “某个内容出现/消失” |
| 5 | 路由变化(布局级) | “前往新的位置” |
这是实现顺序,不是“任选其一”列表。实现所有适用于应用的模式。仅当应用没有对应的使用场景时,才跳过该模式。
选择动画样式
| 上下文 | 动画 | 原因 |
|---|---|---|
| 层级导航(列表 → 详情) | 按类型区分的 nav-forward / nav-back | 表达空间深度 |
| 横向导航(标签页之间切换) | 裸 <ViewTransition>(淡入淡出)或 default="none" | 无需表达深度 |
| Suspense 显示 | enter/exit 字符串属性 | 内容进入 |
| 重新验证/后台刷新 | default="none" | 静默更新——无需动画 |
仅在层级导航(列表 → 详情)和有序序列(上一张/下一张照片、轮播图、分页结果)中使用方向性滑动。对于有序序列,方向表达位置:“下一项”从右侧滑入,“上一项”从左侧滑入。横向/无序导航(标签页之间切换)不应使用方向性滑动——这会错误地暗示空间深度。
可用性
- **Next.js:**不要安装
react@canary——App Router 已在内部捆绑 React canary。ViewTransition开箱即用。npm ls react可能显示一个看起来稳定的版本,这是预期行为。 - **不使用 Next.js:**安装
react@canary react-dom@canary(稳定版 React 中没有ViewTransition)。 - 浏览器支持:Chromium 111+、Firefox 144+、Safari 18.2+。不支持的浏览器会平稳降级。
实现工作流
向现有应用添加视图过渡时,请逐步遵循 references/implementation.md。 从审查开始——不要跳过。将 references/css-recipes.md 中的 CSS 配方复制到全局样式表——不要自行编写动画 CSS。
核心概念
<ViewTransition> 组件
import { ViewTransition } from 'react';
<ViewTransition>
<Component />
</ViewTransition>
React 会自动分配唯一的 view-transition-name,并在幕后调用 document.startViewTransition。切勿自行调用 startViewTransition。
动画触发条件
| 触发条件 | 触发时机 |
|---|---|
| enter | <ViewTransition> 在 Transition 期间首次插入 |
| exit | <ViewTransition> 在 Transition 期间首次移除 |
| update | <ViewTransition> 内部发生 DOM 变更。对于嵌套的 VT,变更作用于最内层 VT |
| share | 已命名的 VT 卸载,且同一 Transition 中挂载了另一个具有相同 name 的 VT |
只有 startTransition、useDeferredValue 或 Suspense 会激活 VT。常规的 setState 不会触发动画。
关键放置规则
只有当 <ViewTransition> 出现在任何 DOM 节点之前时,才会触发 enter/exit:
// Works
<ViewTransition enter="auto" exit="auto">
<div>Content</div>
</ViewTransition>
// Broken — div wraps the VT, suppressing enter/exit
<div>
<ViewTransition enter="auto" exit="auto">
<div>Content</div>
</ViewTransition>
</div>
使用 View Transition 类设置样式
属性
取值:"auto"(浏览器交叉淡入淡出)、"none"(禁用)、"class-name"(自定义 CSS),或用于按类型指定动画的 { [type]: value }。
<ViewTransition default="none" enter="slide-in" exit="slide-out" share="morph" />
如果 default 为 "none",除非明确列出,否则所有触发条件均处于关闭状态。
CSS 伪元素
::view-transition-old(.class)— 离场快照::view-transition-new(.class)— 入场快照::view-transition-group(.class)— 容器::view-transition-image-pair(.class)— 新旧快照对
可参阅 references/css-recipes.md,了解可直接使用的动画方案。
Transition 类型
使用 addTransitionType 为转换添加类型标记,这样 VT 就能根据上下文选择不同的动画。可多次调用以叠加类型 — 树中的不同 VT 会对不同类型作出响应:
startTransition(() => {
addTransitionType('nav-forward');
addTransitionType('select-item');
router.push('/detail/1');
});
传入一个对象,将类型映射到 CSS 类。enter、exit 和 share 都适用:
<ViewTransition
enter={{ 'nav-forward': 'slide-from-right', 'nav-back': 'slide-from-left', default: 'none' }}
exit={{ 'nav-forward': 'slide-to-left', 'nav-back': 'slide-to-right', default: 'none' }}
share={{ 'nav-forward': 'morph-forward', 'nav-back': 'morph-back', default: 'morph' }}
default="none"
>
<Page />
</ViewTransition>
enter 和 exit 无需保持对称。例如,可以淡入,但按方向滑出:
<ViewTransition
enter={{ 'nav-forward': 'fade-in', 'nav-back': 'fade-in', default: 'none' }}
exit={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
default="none"
>
TypeScript: ViewTransitionClassPerType 要求对象中包含 default 键。
对于有多个页面的应用,可将按类型区分的 VT 提取为可复用的包装器:
export function DirectionalTransition({ children }: { children: React.ReactNode }) {
return (
<ViewTransition
enter={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
exit={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
default="none"
>
{children}
</ViewTransition>
);
}
router.back() 和浏览器后退按钮
router.back() 和浏览器的后退/前进按钮不会触发视图转换(popstate 是同步的,与 startViewTransition 不兼容)。请改用带有明确 URL 的 router.push()。
类型与 Suspense
导航期间可以使用类型,但后续 Suspense 显示内容时无法使用类型(这是独立的转换,不带类型)。页面级别的进入/退出请使用类型映射;Suspense 显示内容时请使用简单的字符串属性。
共享元素转换
两个 VT 使用相同的 name,一个卸载,另一个挂载 — 即可创建共享元素变形效果:
<ViewTransition name="hero-image">
<img src="/thumb.jpg" onClick={() => startTransition(() => onSelect())} />
</ViewTransition>
// On the other view — same name
<ViewTransition name="hero-image">
<img src="/full.jpg" />
</ViewTransition>
- 同一时间只能挂载一个具有给定
name的 VT — 请使用唯一名称(photo-${id})。留意可复用组件:如果带有命名 VT 的组件同时渲染在模态框/弹出层和页面中,两处就会同时挂载,从而导致变形效果异常。可以通过属性让名称具备条件性,也可以将命名 VT 从共享组件移到具体的使用方中。 share优先于enter/exit。请考虑每条导航路径:如果没有匹配的配对(例如目标页面没有相同的名称),则会触发enter/exit。请考虑这些路径下元素是否需要备用动画。- 对含有共享变形效果的页面,切勿使用淡出式退出;请改用带方向的滑动效果。
常见模式
进入/退出
{show && (
<ViewTransition enter="fade-in" exit="fade-out"><Panel /></ViewTransition>
)}
列表重排
{items.map(item => (
<ViewTransition key={item.id}><ItemCard item={item} /></ViewTransition>
))}
在 startTransition 内触发。避免在列表与 VT 之间插入包装用的 <div>。
组合共享元素与列表标识
共享元素和列表标识是彼此独立的问题 — 不要混为一谈。列表项包含共享元素时(例如,变形为详情视图的图片),请使用两个嵌套的 <ViewTransition> 边界:
{items.map(item => (
<ViewTransition key={item.id}> {/* list identity */}
<Link href={`/items/${item.id}`}>
<ViewTransition name={`item-image-${item.id}`} share="morph"> {/* shared element */}
<Image src={item.image} />
</ViewTransition>
<p>{item.name}</p>
</Link>
</ViewTransition>
))}
外层 VT 负责列表重排/进入动画。内层 VT 负责跨路由的共享元素变形。缺少任一层都会导致相应动画无声地失效。
使用 key 强制重新进入
<ViewTransition key={searchParams.toString()} enter="slide-up" default="none">
<ResultsGrid />
</ViewTransition>
注意: 如果包装的是 <Suspense>,更改 key 会重新挂载边界并重新获取数据。
Suspense 回退内容转为实际内容
简单交叉淡入淡出:
<ViewTransition>
<Suspense fallback={<Skeleton />}><Content /></Suspense>
</ViewTransition>
带方向的显示:
<Suspense fallback={<ViewTransition exit="slide-down"><Skeleton /></ViewTransition>}>
<ViewTransition enter="slide-up" default="none"><Content /></ViewTransition>
</Suspense>
更多模式请参阅 references/patterns.md。
多个 VT 如何交互
每个与触发条件匹配的 VT 都会在同一个 document.startViewTransition 中同时触发。不同转换中的 VT(导航与之后的 Suspense 解析)不会相互竞争。
尽可能使用 default="none"
不使用它时,每次转换都会触发浏览器的交叉淡入淡出 — Suspense 解析、useDeferredValue 更新、后台重新验证皆是如此。请始终使用 default="none",并仅显式启用所需的触发条件。
两种模式共存
模式 A——方向性幻灯片: 每个页面上按类型设置 VT,在导航时触发。 模式 B——悬念揭示: 使用简单的字符串属性,在数据加载时触发(无类型)。
它们之所以能够共存,是因为触发时机不同。两者都设置 default="none",以防相互干扰。始终将 enter 与 exit 配对。方向性 VT 应放在页面组件中,而不是布局中。
嵌套 VT 的限制
父级 VT 退出时,其中的嵌套 VT 不会触发自身的 enter/exit——只有最外层的 VT 会播放动画。目前无法在页面导航期间为各项设置交错动画。有关实验性的选择加入修复方案,请参阅 react#36135。
Next.js 集成
有关 Next.js 设置(experimental.viewTransition 标志、next/link 上的 transitionTypes 属性、App Router 模式、Server Components),请参阅 references/nextjs.md。
无障碍
请务必将 references/css-recipes.md 中的减少动态效果 CSS 添加到全局样式表。
参考文件
references/implementation.md— 逐步实现工作流。references/patterns.md— 模式、动画时序、事件 API、故障排查。references/css-recipes.md— 可直接使用的 CSS 动画配方。references/nextjs.md— Next.js App Router 模式和 Server Component 详情。
完整编译文档
要查看包含所有参考文件完整内容的指南,请参阅:AGENTS.md