# 实施工作流 为应用添加视图过渡时,请按以下步骤顺序操作。每一步都建立在之前步骤的基础之上。 ## 步骤 1:审查应用 在编写任何代码之前,彻底扫描代码库。搜索: - **每一个 `` 和 `router.push`** —— 这些是你的导航触发器。打开包含它们的每个文件。 - **每一个 `` 边界** —— 每一个都是展示动画的候选对象。检查其 fallback 渲染了什么。 - **每一个页面/路由组件** —— 将它们全部列出。每个页面都需要一个 VT 放置决策。 - **持久化元素** —— 在导航间保持停留在屏幕上的 header、navbar、sidebar、粘性控件。这些需要 `viewTransitionName` 隔离。 - **共享视觉元素** —— 在源视图和目标视图中都出现的图像、卡片或头像(例如列表中的缩略图和详情页上的同一张图片)。 - **骨架屏与内容控件对** —— 如果 Suspense fallback 渲染了一个控件(搜索输入框、标签栏),而实际内容中也有同样的控件,那么两者都需要一个匹配的 `viewTransitionName`。 然后对每个导航进行分类,并生成一份导航映射表: ``` | 路由 | 导航至 | 方向 | VT 模式 | |---------------------|----------------------|-------------|------------------------| | / | /detail/[id] | 前进 | 定向滑动 (directional slide) | | /detail/[id] | / | 后退 | 定向滑动 (directional slide) | | /detail/[id] | /detail/[other] | 顺序切换 | 定向滑动 (有序 prev/next) 或 key+share 交叉淡变 | | /tab/[a] | /tab/[b] | 横向切换 | key+share 交叉淡变 | | (Suspense) | (内容加载) | — | 上滑展示 (slide-up reveal) | ``` 对于每个共享元素(`name` 属性),记录哪些导航会形成配对、哪些不会——这决定了你是否需要将 `enter`/`exit` 作为 `share` 的备用方案。 ## 步骤 2:添加 CSS 配方 将 `css-recipes.md` 中的**完整** CSS 配方集复制到你的全局样式表中。这包括计时变量、共享关键帧、淡入淡出、滑动(垂直)、定向导航(前进/后退)、共享元素变形、持久化元素隔离以及减弱动效。 不要自行编写动画 CSS——这些配方已经处理了错开计时、变形时的运动模糊以及容易出错的减弱动效。你可以在初始设置之后自定义计时变量(`--duration-exit`、`--duration-enter`、`--duration-move`)。 ## 步骤 3:隔离持久化元素 对于步骤 1 中识别出的每个持久化元素,添加 `viewTransitionName` 样式以将其从页面内容的过渡快照中提取出来: ```jsx
...
``` 然后添加 `css-recipes.md` 中的持久化元素隔离 CSS(防止该元素在页面过渡期间产生动画)。如果该元素使用了 `backdrop-blur` 或 `backdrop-filter`,请改用 `css-recipes.md` 中的 backdrop-blur 变通方案。 如果 Suspense fallback 镜像了一个持久化控件(例如骨架搜索输入框),请为实际控件和骨架屏设置相同的 `viewTransitionName`,使它们在原位变形。 ## 步骤 4:添加定向页面过渡 对于步骤 1 中识别出的层级导航,使用 `startTransition` 内部的 `addTransitionType` 标记导航方向: ```jsx startTransition(() => { addTransitionType('nav-forward'); router.push('/detail/1'); }); ``` 然后将每个**页面组件**(而非布局)包裹在类型键控的 `` 中: ```jsx
...页面内容...
``` `css-recipes.md` 中的 `nav-forward` 和 `nav-back` CSS 类会产生水平滑动效果。对于不需要定向动效的简单应用,一个空的 `` 包裹搭配 `enter="fade-in"` / `exit="fade-out"` 也同样可行。 将其提取为可复用组件,这样每个页面就不必重复冗长的类型映射: ```jsx export function DirectionalTransition({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` 这也成为了将来添加新过渡类型时进行修改的唯一入口点。 **规则:** - 始终将 `enter` 与 `exit` 配对使用——没有退出动画,旧页面会瞬间消失,而新页面则动画进入。 - 始终在类型映射对象中包含 `default: "none"`,并在组件上设置 `default="none"`——否则它会在每次过渡时触发。 - 将定向的 `` 放置在**每个页面组件**中,而不是布局中。布局在导航间持久存在,永远不会触发 enter/exit。 - 仅对层级导航或有序序列(prev/next)使用定向滑动。横向/同级导航(tab 到 tab)应使用空的 ``(交叉淡变)或 `default="none"`。 ## 步骤 5:添加 Suspense 展示 对于步骤 1 中识别出的每个 `` 边界,将 fallback 和内容分别包裹在各自的 `` 中: ```jsx } > ``` 此示例使用 `slide-down` / `slide-up` 实现定向垂直动效。对于更简单的展示,一个包裹 `` 的空 `` 即可实现零配置的交叉淡变。根据空间含义进行选择——请查阅主技能文件中的"选择正确的动画样式"表格。 **规则:** - 始终在内容 `` 上使用 `default="none"`,以防止在重新验证或不相关的过渡中重新触发动画。 - 在 Suspense `` 上使用简单的字符串属性(而非类型映射)——Suspense 的解析作为独立的、没有类型的过渡触发,因此类型键控属性无法匹配。 ## 步骤 6:添加共享元素过渡 对于步骤 1 中识别出的每个共享视觉元素,在源视图和目标视图上添加匹配的命名 `` 包裹: ```jsx // 在源视图上(例如列表/网格页面) // 在目标视图上(例如详情页)——相同的 name ``` `share="morph"` 类使用 `css-recipes.md` 中的变形配方(受控时长 + 运动模糊)。对于更简单的交叉淡变,使用 `share="auto"`(浏览器默认行为)。 当列表项包含共享元素时,使用两个嵌套的 `` 层组合两种模式——请参阅 `SKILL.md` 中的"将共享元素与列表标识组合使用"。 **规则:** - 名称必须是全局唯一的——使用 `photo-${id}` 等前缀。 - 在列表侧的共享元素上添加 `default="none"`,以防止在过滤/搜索更新时每个项目都产生交叉淡变。 ## 步骤 7:验证每个导航路径 对照步骤 1 中的导航映射表,逐行确认: - VT 在此导航中是挂载/卸载,还是保持挂载状态(相同路由)? - 对于命名 VT:是否形成了共享配对?如果没有,`enter`/`exit` 是否提供了备用方案? - `default="none"` 是否阻止了你实际想要的动画? - 持久化元素是否保持静态(不随页面内容滑动)? - Suspense 展示动画是否独立于定向导航? 如果任何路径没有产生动画或产生了冲突的动画,请重新审视相关步骤。 --- ## 常见错误 - **不带属性的空 ``** ——没有 `default="none"`,它会在每次过渡(每次导航、每次 Suspense 解析、每次重新验证)时触发浏览器的默认交叉淡变。始终设置 `default="none"` 并仅显式启用你想要的触发器。 - **在布局中使用定向 ``** ——布局在导航间持久存在,永远不会卸载/重新挂载。`enter`/`exit` 属性不会在路由变化时触发。将外层的类型键控 `` 放置在**每个页面组件**中。 - **淡出退出与共享元素变形共存** ——页面溶解与变形冲突。请改用定向滑动退出。 - **自行编写自定义动画 CSS** ——`css-recipes.md` 中的配方已经处理了错开计时、变形时的运动模糊以及减弱动效。直接复制它们,不要重新发明。 - **类型键控对象中缺少 `default: "none"`** ——TypeScript 要求有 `default` 键,没有它时回退值为 `"auto"`,会在每次过渡时触发。 - **在 Suspense 展示中使用类型映射** ——Suspense 的解析作为独立的、没有类型的过渡触发。类型键控属性无法匹配——请改用简单的字符串属性。 - **使用原生的 `viewTransitionName` CSS 触发动画** ——只有当 `` 组件存在于渲染树中时,React 才会调用 `document.startViewTransition`。单纯的 `viewTransitionName` 样式用于将元素从父级快照中隔离出来,而非用于触发动画。 - **同路由导航的 `update` 触发器** ——内容内部嵌套的 VT 会劫持父级的突变,因此 `update` 永远不会在外层 VT 上触发。请改用 `key` + `name` + `share`。 - **在可复用组件中使用命名 VT** ——如果一个带有命名 VT 的组件同时在模态框/弹出层**和**页面中渲染,两者会同时挂载,破坏变形效果。请使 name 变为条件性,或将其移至具体的消费者处。 - **使用 `router.back()` 进行后退导航** ——`router.back()` 会触发同步的 `popstate`,与视图过渡不兼容。请使用带显式 URL 的 `router.push()`。 --- 关于 Next.js 特定的实施步骤(配置标志、`` 上的 `transitionTypes`、同路由动态段),请参阅 `nextjs.md`。