过渡

使用 Vue 或原生浏览器的 View Transitions 在页面和布局之间应用过渡。
Nuxt 使用 Vue 的 <Transition> 组件在页面和布局之间应用过渡。
由于 Nuxt 使用的是 Vue 的 <Transition> 组件,你想要设置动画的页面或布局必须只有一个根元素。具有多个根元素(即 fragment)的页面或布局无法执行动画,因此过渡不会运行,在路由之间导航时可能会报错。Nuxt 会在开发环境中对此发出警告。请将模板包裹在单个根元素中(例如 <div>)。

页面过渡

你可以启用页面过渡,为所有 pages 自动应用过渡。

nuxt.config.ts
export default defineNuxtConfig({
  app: {
    pageTransition: { name: 'page', mode: 'out-in' },
  },
})
如果你在切换页面的同时也更改了布局,那么这里设置的页面过渡将不会运行。你应该改为设置 布局过渡

要开始为页面之间添加过渡,请将以下 CSS 添加到你的 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>

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

要为某个页面设置不同的过渡,请在该页面的 definePageMeta 中设置 pageTransition 键:

<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'rotate',
  },
})
</script>

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

布局过渡

你可以启用布局过渡,为所有 layouts 自动应用过渡。

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

要开始为页面和布局之间添加过渡,请将以下 CSS 添加到你的 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>

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

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

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

Global Settings

You can use nuxt.config to globally customize these default transition names.

The two keys pageTransition and layoutTransition both accept TransitionProps values that can be serialized to JSON. You can pass name, mode, and other valid transition props for custom CSS transitions through them.

nuxt.config.ts
export default defineNuxtConfig({
  app: {
    pageTransition: {
      name: 'fade',
      mode: 'out-in', // default
    },
    layoutTransition: {
      name: 'slide',
      mode: 'out-in', // default
    },
  },
})
If you change the name property, you must also rename the CSS classes accordingly.

To override global transition properties, use definePageMeta to define page or layout transitions for individual Nuxt pages, thereby overriding any page or layout transitions globally defined in the nuxt.config file.

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

禁用过渡

可以针对特定路由禁用 pageTransitionlayoutTransition

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

或者在 nuxt.config 中全局禁用:

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

JavaScript 钩子

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

这种方式非常适合与 JavaScript 动画库(例如 GSAP)配合使用。

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>
了解有关 Transition 组件中可用的额外 JavaScript 钩子 的更多信息。

动态过渡

要使用条件逻辑应用动态过渡,你可以利用内联 middleware 将不同的过渡名称分配给 to.meta.pageTransition

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

当前往下一个 id 时,页面将应用 slide-left 过渡;返回上一个 id 时应用 slide-right

使用 NuxtPage 的过渡

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

app/app.vue
<template>
  <div>
    <NuxtLayout>
      <NuxtPage
        :transition="{
          name: 'bounce',
          mode: 'out-in',
        }"
      />
    </NuxtLayout>
  </div>
</template>
请记住,此页面过渡无法在各个页面上使用 definePageMeta 覆盖。

View Transitions API(实验性)

Nuxt 提供了对 View Transitions API(参见 MDN)的实验性实现。这是一种令人兴奋的新方式来实现原生浏览器过渡,它(除其他功能外)能够在不同页面上对不相关的元素进行过渡。

你可以在 StackBlitz 查看演示。

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

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

可能的值为:falsetrue'always'

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

默认情况下,视图过渡为所有 pages 启用,但你可以设置不同的全局默认值。

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

也可以通过在页面的 definePageMeta 中设置 viewTransition 键来覆盖该页面的默认 viewTransition 值:

pages/about.vue
<script setup lang="ts">
definePageMeta({
  viewTransition: false,
})
</script>
按页面覆盖视图过渡只有在你已启用 experimental.viewTransition 选项时才会生效。

View Transition Types v4.4

View transition types 允许你根据导航类型应用不同的 CSS 动画。这对于创建非对称过渡(例如,前进和后退时使用不同的动画)非常有用。

类型设置在 ViewTransition 上,并可通过 CSS 中的 :active-view-transition-type() 伪类选择器进行针对。

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

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

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

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

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

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 中针对这些类型进行样式设置:

/* 默认交叉渐变 */
::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;
  }
}
typestoTypesfromTypes 的函数值只在 definePageMeta 中有效,不能用于 nuxt.config.ts,后者只支持静态的字符串数组。

page:view-transition:start 钩子提供了对 ViewTransition 对象的访问,该对象包含一个可读写的 types 属性(ViewTransitionTypeSet),你可以在运行时读取或修改它:

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

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

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

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

已知问题

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