mcpField
V
vercel-react-view-transitions

vercel-react-view-transitions

github.com/julianromli设计与 UI21369 次安装4 天前更新
代码仓库

关于 vercel-react-view-transitions

面向 React 应用开发者的技能指南,介绍如何使用浏览器原生 View Transitions 在 UI 状态间制作动画。内容涵盖共享元素、列表变化、Suspense 和路由切换,并强调动画适用场景、无障碍与浏览器降级。

主要功能

  1. 01原生 View Transitions 集成
  2. 02共享元素与列表动画
  3. 03Suspense 和路由切换模式
  4. 04按转换类型配置 CSS 动画
  5. 05无障碍与浏览器降级指引

适用场景

  1. 01为列表到详情页添加连续转场
  2. 02展示 Suspense 内容加载过程
  3. 03为列表重排设计身份动画
  4. 04审查现有应用的转场实现

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/julianromli/ai-skills --skill vercel-react-view-transitions

安装后在 Claude Code 中输入/vercel-react-view-transitions确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

AGENTS.md
metadata.json
README.md
references/
css-recipes.md
implementation.md
nextjs.md
patterns.md

详细介绍

React View Transitions

使用浏览器原生的 document.startViewTransition 在 UI 状态之间添加动画。用 <ViewTransition> 声明是什么,用 startTransition / useDeferredValue / Suspense 触发何时,用 CSS 类控制如何。不支持的浏览器会平稳跳过动画。

何时添加动画

每个 <ViewTransition> 都应表达空间关系或连续性。如果你无法说明它表达了什么,就不要添加。

按以下顺序实现此列表中所有适用的模式:

优先级模式表达的含义
1共享元素(name)“同一事物——深入查看”
2Suspense 显示“数据已加载”
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

评论

支持 Markdown