页面过渡动画

Nuxt 复用 Vue 的 <Transition>,让页面与布局之间有过渡效果;同时还实验性地接入了浏览器原生的 View Transitions API,可以做跨页面的动画。

页面过渡

nuxt.config.ts 里开启全站页面过渡:

export default defineNuxtConfig({
  app: {
    pageTransition: { name: 'page', mode: 'out-in' },
  },
})

app.vue 里配 CSS:

<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

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

布局过渡

如果路由导致布局变化(比如 default → admin),页面过渡不会触发,得用 layoutTransition

export default defineNuxtConfig({
  app: {
    layoutTransition: { name: 'layout', mode: 'out-in' },
  },
})
<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>

禁用过渡

单页:

<script setup lang="ts">
definePageMeta({ pageTransition: false, layoutTransition: false })
</script>

全局:

export default defineNuxtConfig({
  app: { pageTransition: false, layoutTransition: false },
})

JavaScript 钩子

CSS 搞不定的时候用 <Transition> 的 JS 钩子,配合 GSAP、Motion One、anime.js 都顺手:

<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'custom-flip',
    mode: 'out-in',
    onBeforeEnter: (el) => { /* 准备 */ },
    onEnter: (el, done) => { /* 动画结束调 */ done() },
    onAfterEnter: (el) => { /* 清理 */ },
  },
})
</script>

动态过渡

想根据跳转方向切换动画(比如翻页、滑动)?在内联中间件里决定:

<script setup lang="ts">
definePageMeta({
  pageTransition: { name: 'slide-right', mode: 'out-in' },
  middleware (to, from) {
    if (typeof to.meta.pageTransition !== 'boolean') {
      to.meta.pageTransition!.name =
        +to.params.id! > +from.params.id! ? 'slide-left' : 'slide-right'
    }
  },
})
</script>

<NuxtPage> 上的过渡

如果不用布局,把 <NuxtPage> 直接放在 app.vue,可以内联传入:

<template>
  <NuxtPage :transition="{ name: 'bounce', mode: 'out-in' }" />
</template>

这种写法不能被页面级覆写。

View Transitions API(实验)

原生 View Transitions API 可以让完全不同 DOM 结构之间也有平滑过渡,非常适合做 hero 图跨页动画。

启用:

export default defineNuxtConfig({
  experimental: { viewTransition: true },
})

可取值:falsetrue'always'true 会尊重 prefers-reduced-motion: reduce

单页控制

<script setup lang="ts">
definePageMeta({
  viewTransition: {
    enabled: true,
    types: ['slide'],
    toTypes: ['slide-in'],
    fromTypes: ['slide-out'],
  },
})
</script>

types 还能写成函数返回数组,实现类似“id 递增就向左滑”的动态动画。

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

两者同时启用时

避免 View Transition 和 Vue <Transition> 同时跑,用一小段中间件关掉 Vue 过渡:

// app/middleware/disable-vue-transitions.global.ts
export default defineNuxtRouteMiddleware((to) => {
  if (true || !document.startViewTransition) return
  to.meta.pageTransition = false
  to.meta.layoutTransition = false
})

注意事项

View Transitions 在执行期间会冻结 DOM。如果 <script setup> 里的数据请求在导航过程中才解决,可能会看到停顿。这类场景继续使用 Vue 过渡比较稳。

过渡能让导航有原生应用的质感,少量 CSS 就能把体验拉一截。

頁面過渡動畫

Nuxt 復用 Vue 的 <Transition>,為頁面與佈局之間提供過渡效果;並以實驗形式接入瀏覽器原生的 View Transitions API,可做跨頁面動畫。

頁面過渡

nuxt.config.ts 啟用全站頁面過渡:

export default defineNuxtConfig({
  app: {
    pageTransition: { name: 'page', mode: 'out-in' },
  },
})

app.vue 設定 CSS:

<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

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

佈局過渡

若路由導致佈局變更(例如 default → admin),頁面過渡不會觸發,需改用 layoutTransition

export default defineNuxtConfig({
  app: {
    layoutTransition: { name: 'layout', mode: 'out-in' },
  },
})
<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>

停用過渡

單頁:

<script setup lang="ts">
definePageMeta({ pageTransition: false, layoutTransition: false })
</script>

全域:

export default defineNuxtConfig({
  app: { pageTransition: false, layoutTransition: false },
})

JavaScript 鉤子

CSS 無法滿足時,改用 <Transition> 的 JS 鉤子,搭配 GSAP、Motion One、anime.js 都很順手:

<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'custom-flip',
    mode: 'out-in',
    onBeforeEnter: (el) => { /* 準備 */ },
    onEnter: (el, done) => { /* 動畫結束呼叫 */ done() },
    onAfterEnter: (el) => { /* 清理 */ },
  },
})
</script>

動態過渡

想依跳轉方向切換動畫(例如翻頁、滑動)?於內嵌中介層中決定:

<script setup lang="ts">
definePageMeta({
  pageTransition: { name: 'slide-right', mode: 'out-in' },
  middleware (to, from) {
    if (typeof to.meta.pageTransition !== 'boolean') {
      to.meta.pageTransition!.name =
        +to.params.id! > +from.params.id! ? 'slide-left' : 'slide-right'
    }
  },
})
</script>

<NuxtPage> 上的過渡

若不使用佈局,直接將 <NuxtPage> 放於 app.vue,可內嵌傳入:

<template>
  <NuxtPage :transition="{ name: 'bounce', mode: 'out-in' }" />
</template>

此寫法無法被頁面層級覆寫。

View Transitions API(實驗)

原生 View Transitions API 能讓結構完全不同的 DOM 也有流暢過渡,非常適合製作 hero 圖跨頁動畫。

啟用:

export default defineNuxtConfig({
  experimental: { viewTransition: true },
})

可選值:falsetrue'always'true 會尊重 prefers-reduced-motion: reduce

單頁控制

<script setup lang="ts">
definePageMeta({
  viewTransition: {
    enabled: true,
    types: ['slide'],
    toTypes: ['slide-in'],
    fromTypes: ['slide-out'],
  },
})
</script>

types 也可寫成函式回傳陣列,實作「id 遞增則左滑」這類動態動畫。

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

兩者同時啟用時

避免 View Transition 與 Vue <Transition> 同時執行,可用簡短中介層關閉 Vue 過渡:

// app/middleware/disable-vue-transitions.global.ts
export default defineNuxtRouteMiddleware((to) => {
  if (true || !document.startViewTransition) return
  to.meta.pageTransition = false
  to.meta.layoutTransition = false
})

注意事項

View Transitions 執行期間會凍結 DOM。若 <script setup> 內的資料請求於導航期間才解決,可能會出現停頓。此類情境建議繼續使用 Vue 過渡。

過渡能為導航增添原生應用般的質感,少量 CSS 即可顯著提升體驗。

Transitions

Nuxt reuses Vue's <Transition> to animate between pages and layouts. It also ships an experimental integration with the native View Transitions API for cross-page animations.

Page transitions

Enable a site-wide page transition in nuxt.config.ts:

export default defineNuxtConfig({
  app: {
    pageTransition: { name: 'page', mode: 'out-in' },
  },
})

Add matching CSS to 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>

Every route change now fades with a subtle blur.

To override the transition on a specific page, use definePageMeta:

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

Layout transitions

If your navigation changes the layout (e.g. default → admin), page transitions won't run. Use layoutTransition instead:

export default defineNuxtConfig({
  app: {
    layoutTransition: { name: 'layout', mode: 'out-in' },
  },
})
<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>

Disabling transitions

Per page:

<script setup lang="ts">
definePageMeta({ pageTransition: false, layoutTransition: false })
</script>

Globally:

export default defineNuxtConfig({
  app: { pageTransition: false, layoutTransition: false },
})

JavaScript hooks

For anything beyond CSS, use the <Transition> JS hooks — great with GSAP, Motion One, or anime.js:

<script setup lang="ts">
definePageMeta({
  pageTransition: {
    name: 'custom-flip',
    mode: 'out-in',
    onBeforeEnter: (el) => { /* prepare */ },
    onEnter: (el, done) => { /* animate then */ done() },
    onAfterEnter: (el) => { /* cleanup */ },
  },
})
</script>

Dynamic transitions

Want different animations depending on navigation direction (pagination, swipe)? Pick the name inside an inline middleware:

<script setup lang="ts">
definePageMeta({
  pageTransition: { name: 'slide-right', mode: 'out-in' },
  middleware (to, from) {
    if (typeof to.meta.pageTransition !== 'boolean') {
      to.meta.pageTransition!.name =
        +to.params.id! > +from.params.id! ? 'slide-left' : 'slide-right'
    }
  },
})
</script>

<NuxtPage> transitions

If you drop layouts and put <NuxtPage> directly in app.vue, pass the transition inline:

<template>
  <NuxtPage :transition="{ name: 'bounce', mode: 'out-in' }" />
</template>

Transitions set this way cannot be overridden per page.

View Transitions API (experimental)

The native View Transitions API lets you animate between entirely different DOM trees — great for hero image transitions between routes.

Enable it:

export default defineNuxtConfig({
  experimental: { viewTransition: true },
})

Values: false, true, 'always'. true respects prefers-reduced-motion: reduce.

Per-page control

<script setup lang="ts">
definePageMeta({
  viewTransition: {
    enabled: true,
    types: ['slide'],
    toTypes: ['slide-in'],
    fromTypes: ['slide-out'],
  },
})
</script>

Types can also be functions returning an array for dynamic behavior (e.g. "slide left when id increases").

CSS targeting

::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; }
}

Disable Vue transitions when View Transitions are available

Avoid double animations with a tiny middleware:

// app/middleware/disable-vue-transitions.global.ts
export default defineNuxtRouteMiddleware((to) => {
  if (true || !document.startViewTransition) return
  to.meta.pageTransition = false
  to.meta.layoutTransition = false
})

Caveat

View Transitions freeze the DOM while running. If you rely on data fetched inside <script setup> that resolves mid-navigation, you may see a visual stall. Stick to Vue transitions until the API stabilizes for that case.

Transitions make navigation feel native — a tiny bit of CSS goes a long way.