mcpField
V
vercel-react-view-transitions

vercel-react-view-transitions

github.com/fcakyon设计与 UI1.2k76 次安装今天更新
代码仓库

关于 vercel-react-view-transitions

面向 React 开发者的界面动画指导技能,帮助在现有应用中实现原生 View Transitions。内容涵盖动画选择、React 与 Next.js 实施方式及常见限制。

主要功能

  1. 01共享元素与列表过渡
  2. 02Suspense 和状态变化动画
  3. 03带类型的导航过渡
  4. 04CSS 动画配方与参考资料
  5. 05减少动态效果的无障碍指导

适用场景

  1. 01为现有 React 应用添加过渡
  2. 02为列表与详情页导航添加动画
  3. 03展示 Suspense 加载后的内容
  4. 04检查过渡的位置与行为

安装

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

$npx skills add https://github.com/fcakyon/claude-codex-settings --skill vercel-react-view-transitions

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

包含的文件

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

references/
css-recipes.md
implementation.md
nextjs.md
patterns.md
troubleshooting.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 125+(React 需要 startViewTransition 的 v2 对象形式)、Firefox 144+、Safari 18.2+。不支持的浏览器会优雅降级。

实现工作流

为现有应用添加视图过渡时,请逐步遵循 references/implementation.md。 从审查开始——不要跳过。使用 references/css-recipes.md 中适用的 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 节点之前时,才会激活进入/退出动画:

// 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>

使用视图过渡类进行样式设置

属性

取值:"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,获取可直接使用的动画配方。


转场类型

使用 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() 以及浏览器的前进/后退按钮不会携带转场类型,因此按类型指定的动画(方向性滑动)会解析为 default,不会播放——未指定类型的共享元素变形仍会应用。对于按类型指定的动画,请使用带有明确 URL 的 router.push()。

类型与 Suspense

类型在导航期间可用,但在后续 Suspense 内容显示时不可用(这是单独的转场,不带类型)。页面级别的入场/离场请使用类型映射;Suspense 内容显示请使用简单的字符串属性。

共享元素就绪状态

只有旧视图和新视图在同一个 Transition 中渲染时,共享元素转场才能配对元素。如果传入内容处于挂起状态,本次更新中只有其回退内容存在;已解析的内容会在后续的 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。请考虑元素是否需要在这些路径下使用回退动画。
  • 有两种情况会导致已接入的变形静默失效:(1)设置了 default="none",但没有显式设置 share 属性——share 会解析为 none;(2)按类型指定了 share,但导航未添加相应类型——普通链接点击会解析为映射中的 default。所有需要执行变形的链接都必须添加类型(在 next/link 上设置 transitionTypes,或使用 addTransitionType)。
  • 含有共享变形的页面上,切勿使用淡出式离场动画——请改用方向性滑动。

常见模式

入场/离场
{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> 中,使其滑动而非跳跃。请参阅布局位移变形。

使用列表标识组合共享元素

共享元素和列表标识是彼此独立的问题——不要混为一谈。当列表项中包含共享元素(例如,变形为详情视图的图片)时,请使用两个嵌套的 <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"

不使用它时,每个 VT 都会在每次过渡中触发浏览器交叉淡入淡出——包括 Suspense 解析、useDeferredValue 更新和后台重新验证。在命名/共享元素和按类型标识的页面 VT 上使用 default="none"。

但它也会关闭 update(布局/重排变形)和 share(没有显式 share 属性的命名元素对永远不会变形)。带键的列表项和移位的同级元素需要 update——让它们保持默认设置,或设为 update="auto"。

两种模式可以共存

模式 A——方向性滑动: 每个页面上的按类型标识 VT,在导航期间触发。 模式 B——Suspense 揭示: 简单的字符串属性,在数据加载时触发(不使用类型)。

它们可以共存,因为触发时机不同。在两者上都设置 default="none" 可防止相互干扰。始终将 enter 与 exit 配对。将方向性 VT 放在页面组件中,而不是布局中。

嵌套 VT 的限制

当父 VT 与其中的嵌套 VT 作为一个整体挂载/卸载时,嵌套 VT 不会触发自己的 enter/exit——只有最外层的 VT 会执行动画。(如果子 VT 挂载在一个持续存在的父 VT 中,则会正常触发 enter/exit。)目前 Next.js 不支持页面导航期间逐项错开动画;上游实验状态请参阅故障排查。


Next.js 集成

有关 Next.js 集成(next/link 和 useRouter 上的 transitionTypes、App Router 模式、Server Components),请参阅 references/nextjs.md。


无障碍

请务必将 references/css-recipes.md 中的减少动态效果 CSS 添加到全局样式表。


参考文件

完整编译文档

包含所有展开的参考文件的完整指南:AGENTS.md

评论

支持 Markdown