Files
2026-07-13 21:35:52 +08:00

6.1 KiB
Raw Permalink Blame History

name, description, metadata
name description metadata
nextjs-view-transitions Next.js 中 View Transitions API 的使用指南
type
reference

Next.js 中的 View Transitions

设置

<ViewTransition> 开箱即用,支持 startTransition/Suspense 更新。若要对 <Link> 导航也启用动画:

// next.config.js
const nextConfig = {
  experimental: { viewTransition: true },
};
module.exports = nextConfig;

这会为每个 <Link> 导航包裹 document.startViewTransition。任何 default="auto" 的 VT 会在每次链接点击时触发——请使用 default="none" 来防止动画冲突。

不要安装 react@canary——详见 SKILL.md 的"可用性"章节。


Next.js 实现补充

在遵循 implementation.md 时,应用以下补充内容:

步骤 2 之后: 启用上述实验性标志。

步骤 4<Link> 上使用 transitionTypes——详见下文"transitionTypes Prop"章节的用法和可用性说明。

步骤 6 之后: 对于相同路由的动态段(例如 /collection/[slug]),使用 key + name + share 模式——详见下文"相同路由动态段过渡"章节。


布局级别的 ViewTransition

如果页面有自己的 VT,不要在布局中添加包裹 {children} 的布局级 VT。 嵌套 VT 在父级 VT 内部时,enter/exit 永远不会触发——页面级的 enter/exit 会静默失效。请完全移除布局级 VT。

一个裸露的 <ViewTransition> 在布局中只有在页面没有自己的 VT 时才能正常工作。

布局在导航间持久存在——enter/exit 仅在初始挂载时触发,路由变化时不触发。不要在布局中使用类型键映射。


无需包装组件,在服务端组件中即可工作:

<Link href="/products/1" transitionTypes={['transition-to-detail']}>查看产品</Link>

这替代了手动 onNavigate + startTransition + addTransitionType + router.push() 的模式。仅在非链接交互(按钮、表单)时保留手动 startTransition

可用性: transitionTypes 需要启用 experimental.viewTransition: true,在 Next.js 15+ canary 构建版本和 Next.js 16+ 中可用。如果不可用,请使用 startTransition + addTransitionType + router.push()(参见下面的"程序化导航"章节)。检查方法:grep -r "transitionTypes" node_modules/next/dist/——如果没有结果,则回退到程序化导航。


程序化导航

'use client';

import { useRouter } from 'next/navigation';
import { startTransition, addTransitionType } from 'react';

function handleNavigate(href: string) {
  const router = useRouter();
  startTransition(() => {
    addTransitionType('nav-forward');
    router.push(href);
  });
}

使用 router.replace 实现服务端筛选

对于通过 URL 参数在服务端重新渲染的搜索/排序/筛选,使用 startTransition + router.replace。VT 会生效,因为状态更新位于 startTransition 内部:

'use client';

import { useRouter } from 'next/navigation';
import { startTransition } from 'react';

function handleSort(sort: string) {
  const router = useRouter();
  startTransition(() => {
    router.replace(`?sort=${sort}`);
  });
}

包裹在 <ViewTransition key={item.id}> 中的列表项会以动画方式重新排序。这是 patterns.md 中客户端 useDeferredValue 模式的服务端组件替代方案。


双层模式(方向动画 + Suspense)

方向滑动动画与 Suspense 展现动画可以共存,因为它们在各自不同的时机触发。请将方向 VT 放在页面组件中(而非布局):

<ViewTransition
  enter={{ "nav-forward": "slide-from-right", default: "none" }}
  exit={{ "nav-forward": "slide-to-left", default: "none" }}
  default="none"
>
  <div>
    <Suspense fallback={<ViewTransition exit="slide-down"><骨架屏 /></ViewTransition>}>
      <ViewTransition enter="slide-up" default="none"><内容 /></ViewTransition>
    </Suspense>
  </div>
</ViewTransition>

loading.tsx 作为 Suspense 边界

Next.js 的 loading.tsx 是一个隐式的 <Suspense> 边界。在 loading.tsx 中,将骨架屏包裹在 <ViewTransition exit="..."> 中;在页面中,将内容包裹在 <ViewTransition enter="..." default="none"> 中:

// loading.tsx
<ViewTransition exit="slide-down"><PhotoGridSkeleton /></ViewTransition>

// page.tsx
<ViewTransition enter="slide-up" default="none"><PhotoGrid photos={photos} /></ViewTransition>

与显式 <Suspense> 规则相同:使用简单字符串 prop(而非类型映射),因为 Suspense 展现动画触发时不带过渡类型。


跨路由共享元素

// 列表页
{products.map((product) => (
  <Link key={product.id} href={`/products/${product.id}`} transitionTypes={['nav-forward']}>
    <ViewTransition name={`product-${product.id}`}>
      <Image src={product.image} alt={product.name} width={400} height={300} />
    </ViewTransition>
  </Link>
))}

// 详情页——相同的 name
<ViewTransition name={`product-${product.id}`}>
  <Image src={product.image} alt={product.name} width={800} height={600} />
</ViewTransition>

相同路由动态段过渡

当在同一路由的不同动态段之间导航时(例如 /collection/[slug]),页面保持挂载状态——enter/exit 永远不会触发。请使用 key + name + share

<Suspense fallback={<骨架屏 />}>
  <ViewTransition key={slug} name={`collection-${slug}`} share="auto" default="none">
    <Content slug={slug} />
  </ViewTransition>
</Suspense>
  • key={slug} 在变化时强制卸载/重新挂载
  • name + share="auto" 创建共享元素的交叉淡入淡出效果
  • 位于 <Suspense> 内部的 VT(不对 Suspense 设置 key)在加载期间保持旧内容可见

服务端组件

  • <ViewTransition> 在服务端组件和客户端组件中均可使用
  • <Link transitionTypes> 在服务端组件中可用——无需 'use client'
  • 程序化导航的 addTransitionTypestartTransition 需要客户端组件