---
title: "过渡"
description: "使用 Vue 或原生浏览器的 View Transitions 在页面和布局之间应用过渡。"
canonical_url: "https://nuxt.zhcndoc.com/docs/4.x/getting-started/transitions"
---
# 过渡

> 使用 Vue 或原生浏览器的 View Transitions 在页面和布局之间应用过渡。

::note
Nuxt 使用 Vue 的 [`<Transition>`](https://vuejs.org/guide/built-ins/transition#the-transition-component) 组件在页面和布局之间应用过渡。
::

::important
由于 Nuxt 使用了 Vue 的 `<Transition>` 组件，你想要设置动画的页面或布局必须只有一个 **单一根元素**。包含多个根元素（fragment）的页面或布局无法设置动画，因此过渡将不会运行，并且在路由之间导航时可能会出错。Nuxt 会在开发环境中对此发出警告。请将模板包裹在一个单一根元素中（例如 `<div>`）。
::

## 页面过渡

你可以启用页面过渡，为所有[页面](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/pages)应用自动过渡。

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  app: {
    pageTransition: { name: 'page', mode: 'out-in' },
  },
})
```

::note
如果你同时更改布局和页面，则此处设置的页面过渡不会运行。此时，你应该设置[布局过渡](https://nuxt.zhcndoc.com/docs/4.x/getting-started/transitions#layout-transitions)。
::

要开始为页面之间添加过渡，请将以下 CSS 添加到你的 [`app.vue`](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/app) 中：

::code-group
```vue [app/app.vue]
<template>
  <NuxtPage />
</template>

<style>
.page-enter-active,
.page-leave-active {
  transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
  opacity: 0;
  filter: blur(1rem);
}
</style>
```

```vue [app/pages/index.vue]
<template>
  <div>
    <h1>首页</h1>
    <NuxtLink to="/about">关于页面</NuxtLink>
  </div>
</template>
```

```vue [app/pages/about.vue]
<template>
  <div>
    <h1>关于页面</h1>
    <NuxtLink to="/">首页</NuxtLink>
  </div>
</template>
```
::

在页面之间导航时会产生如下效果：

<video controls class="rounded" poster="https://res.cloudinary.com/nuxt/video/upload/v1665061349/nuxt3/nuxt3-page-transitions_umwvmh.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665061349/nuxt3/nuxt3-page-transitions_umwvmh.mp4" type="video/mp4">
</video>

要为页面设置不同的过渡，请在该页面的 [`definePageMeta`](https://nuxt.zhcndoc.com/docs/4.x/api/utils/define-page-meta) 中设置 `pageTransition` 键：

::code-group
```vue twoslash [pages/about.vue]
<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'rotate',
  },
})
</script>
```

```vue [app/app.vue]
<template>
  <NuxtPage />
</template>

<style>
/* ... */
.rotate-enter-active,
.rotate-leave-active {
  transition: all 0.4s;
}
.rotate-enter-from,
.rotate-leave-to {
  opacity: 0;
  transform: rotate3d(1, 1, 1, 15deg);
}
</style>
```
::

切换到 about 页面时会添加 3D 旋转效果：

<video controls class="rounded" poster="https://res.cloudinary.com/nuxt/video/upload/v1665063233/nuxt3/nuxt3-page-transitions-cutom.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665063233/nuxt3/nuxt3-page-transitions-cutom.mp4" type="video/mp4">
</video>

## 布局过渡

你可以启用布局过渡，为所有[布局](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/layouts)应用自动过渡。

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  app: {
    layoutTransition: { name: 'layout', mode: 'out-in' },
  },
})
```

要开始为页面和布局之间添加过渡，请将以下 CSS 添加到你的 [`app.vue`](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/app) 中：

::code-group
```vue [app/app.vue]
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

<style>
.layout-enter-active,
.layout-leave-active {
  transition: all 0.4s;
}
.layout-enter-from,
.layout-leave-to {
  filter: grayscale(1);
}
</style>
```

```vue [app/layouts/default.vue]
<template>
  <div>
    <pre>默认布局</pre>
    <slot />
  </div>
</template>

<style scoped>
div {
  background-color: lightgreen;
}
</style>
```

```vue [app/layouts/orange.vue]
<template>
  <div>
    <pre>橙色布局</pre>
    <slot />
  </div>
</template>

<style scoped>
div {
  background-color: #eebb90;
  padding: 20px;
  height: 100vh;
}
</style>
```

```vue [app/pages/index.vue]
<template>
  <div>
    <h1>首页</h1>
    <NuxtLink to="/about">关于页面</NuxtLink>
  </div>
</template>
```

```vue [app/pages/about.vue]
<script setup lang="ts">
definePageMeta({
  layout: 'orange',
})
</script>

<template>
  <div>
    <h1>关于页面</h1>
    <NuxtLink to="/">首页</NuxtLink>
  </div>
</template>
```
::

在页面之间导航时会产生如下效果：

<video controls class="rounded" poster="https://res.cloudinary.com/nuxt/video/upload/v1665065289/nuxt3/nuxt3-layouts-transitions_c9hwlx.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665065289/nuxt3/nuxt3-layouts-transitions_c9hwlx.mp4" type="video/mp4">
</video>

类似于 `pageTransition`，你可以在页面组件中使用 `definePageMeta` 应用自定义的 `layoutTransition`：

```vue twoslash [pages/about.vue]
<script setup lang="ts">
definePageMeta({
  layout: 'orange',
  layoutTransition: {
    name: 'slide-in',
  },
})
</script>
```

## 全局设置

你可以使用 `nuxt.config` 全局自定义这些默认过渡名称。

`pageTransition` 和 `layoutTransition` 两个键都接受可 JSON 序列化的 [`TransitionProps`](https://vuejs.org/api/built-in-components#transition) 值，你可以通过它们传递 `name`、`mode` 以及自定义 CSS 过渡的其他有效 transition-props。

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  app: {
    pageTransition: {
      name: 'fade',
      mode: 'out-in', // 默认
    },
    layoutTransition: {
      name: 'slide',
      mode: 'out-in', // 默认
    },
  },
})
```

::warning
如果你更改了 `name` 属性，也必须相应地重命名 CSS 类。
::

要覆盖全局过渡属性，请使用 `definePageMeta` 为单个 Nuxt 页面定义页面或布局过渡，从而覆盖在 `nuxt.config` 文件中全局定义的任何页面或布局过渡。

```vue twoslash [pages/some-page.vue]
<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'bounce',
    mode: 'out-in', // 默认
  },
})
</script>
```

## 禁用过渡

可以针对特定路由禁用 `pageTransition` 和 `layoutTransition`：

```vue twoslash [pages/some-page.vue]
<script setup lang="ts">
definePageMeta({
  pageTransition: false,
  layoutTransition: false,
})
</script>
```

或者在 `nuxt.config` 中全局禁用：

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  app: {
    pageTransition: false,
    layoutTransition: false,
  },
})
```

## JavaScript 钩子

对于高级用例，你可以使用 JavaScript 钩子为 Nuxt 页面创建高度动态和自定义的过渡。

这种方式非常适合与 JavaScript 动画库（例如 [GSAP](https://gsap.com)）配合使用。

```vue twoslash [pages/some-page.vue]
<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'custom-flip',
    mode: 'out-in',
    onBeforeEnter: (el) => {
      console.log('进入前...')
    },
    onEnter: (el, done) => {},
    onAfterEnter: (el) => {},
  },
})
</script>
```

::tip
了解有关 `Transition` 组件中可用的额外 [JavaScript 钩子](https://vuejs.org/guide/built-ins/transition#javascript-hooks) 的更多信息。
::

## 动态过渡

要使用条件逻辑应用动态过渡，可以借助内联 [middleware](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/middleware)，为 `to.meta.pageTransition` 指定不同的过渡名称。

::code-group
```vue twoslash [pages/[id\\].vue]
<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'slide-right',
    mode: 'out-in',
  },
  middleware (to, from) {
    if (to.meta.pageTransition && typeof to.meta.pageTransition !== 'boolean') {
      to.meta.pageTransition.name = +to.params.id! > +from.params.id! ? 'slide-left' : 'slide-right'
    }
  },
})
</script>

<template>
  <h1>#{{ $route.params.id }}</h1>
</template>

<style>
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
  transition: all 0.2s;
}
.slide-left-enter-from {
  opacity: 0;
  transform: translate(50px, 0);
}
.slide-left-leave-to {
  opacity: 0;
  transform: translate(-50px, 0);
}
.slide-right-enter-from {
  opacity: 0;
  transform: translate(-50px, 0);
}
.slide-right-leave-to {
  opacity: 0;
  transform: translate(50px, 0);
}
</style>
```

```vue [app/layouts/default.vue]
<script setup lang="ts">
const route = useRoute()
const id = computed(() => Number(route.params.id || 1))
const prev = computed(() => '/' + (id.value - 1))
const next = computed(() => '/' + (id.value + 1))
</script>

<template>
  <div>
    <slot />
    <div v-if="$route.params.id">
      <NuxtLink :to="prev">⬅️</NuxtLink> |
      <NuxtLink :to="next">➡️</NuxtLink>
    </div>
  </div>
</template>
```
::

该页面现在在跳转到下一个 id 时应用 `slide-left` 过渡，在跳转到上一个 id 时应用 `slide-right` 过渡：

<video controls class="rounded" poster="https://res.cloudinary.com/nuxt/video/upload/v1665069410/nuxt3/nuxt-dynamic-page-transitions.jpg">
<source src="https://res.cloudinary.com/nuxt/video/upload/v1665069410/nuxt3/nuxt-dynamic-page-transitions.mp4" type="video/mp4">
</video>

## 使用 NuxtPage 的过渡

当在 `app.vue` 中使用 `<NuxtPage />` 时，可以通过 `transition` 属性配置过渡，以在全局启用过渡。

```vue [app/app.vue]
<template>
  <div>
    <NuxtLayout>
      <NuxtPage
        :transition="{
          name: 'bounce',
          mode: 'out-in',
        }"
      />
    </NuxtLayout>
  </div>
</template>
```

::note
请记住，此页面过渡无法在各个页面上使用 `definePageMeta` 覆盖。
::

## View Transitions API（实验性）

Nuxt 提供了对 [**View Transitions API**](https://developer.chrome.com/docs/web-platform/view-transitions)（参见 [MDN](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API)）的实验性实现。这是一种令人兴奋的新方式来实现原生浏览器过渡，它（除其他功能外）能够在不同页面上对不相关的元素进行过渡。

你可以在 [StackBlitz](https://stackblitz.com/edit/nuxt-view-transitions) 查看演示。

Nuxt 集成可以通过在配置文件中设置 `experimental.viewTransition` 选项来启用：

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  experimental: {
    viewTransition: true,
  },
})
```

可能的值为：`false`、`true` 或 `'always'`。

如果设置为 true，Nuxt 在用户的浏览器匹配 `prefers-reduced-motion: reduce` 时将不会应用过渡（推荐）。如果设置为 `always`，Nuxt 将始终应用过渡，此时由你来尊重用户的偏好。

默认情况下，所有[页面](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/pages)都启用了视图过渡，但你可以设置不同的全局默认值。

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  app: {
    // 全局禁用视图过渡，按页面选择启用
    viewTransition: false,
  },
})
```

你可以在页面的 [`definePageMeta`](https://nuxt.zhcndoc.com/docs/4.x/api/utils/define-page-meta) 中设置 `viewTransition` 键，以覆盖该页面的默认 `viewTransition` 值：

```vue twoslash [pages/about.vue]
<script setup lang="ts">
definePageMeta({
  viewTransition: false,
})
</script>
```

::warning
按页面覆盖视图过渡只有在你已启用 `experimental.viewTransition` 选项时才会生效。
::

### View Transition Types :badge[v4.4]{color="info" size="xs" .align-middle}

[视图过渡类型](https://developer.chrome.com/blog/view-transitions-update-io24#view-transition-types)允许你根据导航类型应用不同的 CSS 动画。这对于创建非对称过渡（例如，前进和后退时使用不同的动画）非常有用。

类型设置在 [`ViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) 上，并可通过 CSS 中的 [`:active-view-transition-type()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:active-view-transition-type) 伪类选择器进行针对。

你可以在 `nuxt.config.ts` 文件中全局设置默认类型：

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  app: {
    viewTransition: {
      enabled: true,
      types: ['slide'],
    },
  },
})
```

或者通过 `definePageMeta` 为单个页面配置类型。页面级类型支持静态数组和动态函数：

```vue twoslash [pages/detail.vue]
<script setup lang="ts">
definePageMeta({
  viewTransition: {
    enabled: true,
    // 该页面涉及的任意过渡应用的类型
    types: ['slide'],
    // 仅在导航到该页面时应用的类型
    toTypes: ['slide-in'],
    // 仅在从该页面导航离开时应用的类型
    fromTypes: ['slide-out'],
  },
})
</script>
```

你也可以在 `definePageMeta` 中使用函数来动态确定类型，基于路由状态：

```vue twoslash [pages/[id].vue]
<script setup lang="ts">
definePageMeta({
  viewTransition: {
    enabled: true,
    toTypes: (to, from) => {
      // 如果跳转到更大 ID，则左滑，否则右滑
      return Number(to.params.id) > Number(from.params.id)
        ? ['slide-left']
        : ['slide-right']
    },
  },
})
</script>
```

然后在 CSS 中针对这些类型进行样式设置：

```css
/* 默认交叉渐变 */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}

/* 左滑动画 */
html:active-view-transition-type(slide-left) {
  &::view-transition-old(root) {
    animation: slide-out-left 0.3s ease-in-out;
  }
  &::view-transition-new(root) {
    animation: slide-in-right 0.3s ease-in-out;
  }
}

/* 右滑动画 */
html:active-view-transition-type(slide-right) {
  &::view-transition-old(root) {
    animation: slide-out-right 0.3s ease-in-out;
  }
  &::view-transition-new(root) {
    animation: slide-in-left 0.3s ease-in-out;
  }
}
```

::note
`types`、`toTypes` 和 `fromTypes` 的函数值只在 `definePageMeta` 中有效，不能用于 `nuxt.config.ts`，后者只支持静态的字符串数组。
::

`page:view-transition:start` 钩子提供了对 [`ViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) 对象的访问，该对象包含一个可读写的 [`types`](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition/types) 属性（`ViewTransitionTypeSet`），你可以在运行时读取或修改它：

```ts [plugins/view-transition.client.ts]
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('page:view-transition:start', (transition) => {
    // 在运行时读取或修改 types
    console.log([...transition.types])
  })
})
```

如果你也使用了像 `pageTransition` 和 `layoutTransition`（见上文）这样的 Vue 过渡来实现与新的 View Transitions API 相同的效果，那么当用户的浏览器支持新的原生 Web API 时，你可能希望禁用 Vue 过渡。你可以通过创建文件 `~/middleware/disable-vue-transitions.global.ts` 并添加如下内容来实现：

```ts
export default defineNuxtRouteMiddleware((to) => {
  if (import.meta.server || !document.startViewTransition) {
    return
  }

  // 禁用内置 Vue 过渡
  to.meta.pageTransition = false
  to.meta.layoutTransition = false
})
```

### 已知问题

- 如果你在页面的 setup 函数中进行数据获取，目前你可能需要重新考虑是否使用此功能。（按设计，视图过渡在进行时会完全冻结 DOM 更新。）我们正在考虑将视图过渡限制在 `<Suspense>` 解析前的最后时刻，但在此期间，如果这描述了你的场景，你可能需要谨慎考虑是否采用此功能。


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
