过渡动画
使用 Vue 或原生浏览器 View Transitions API 在页面和布局之间应用过渡动画。
Nuxt 利用 Vue 的 <Transition> 组件来在页面和布局之间应用过渡动画。
因为 Nuxt 使用 Vue 的 <Transition> 组件,要做动画的页面或布局必须有单个根元素。有多个根元素(片段)的页面或布局无法做动画,过渡不会执行,路由切换时可能报错。Nuxt 会在开发环境警告你。把模板包裹在单个根元素中(比如一个 <div>)。
页面过渡
你可以启用页面过渡,为所有页面自动应用过渡动画。
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>
<template>
<div>
<h1>首页</h1>
<NuxtLink to="/about">关于页面</NuxtLink>
</div>
</template>
<template>
<div>
<h1>关于页面</h1>
<NuxtLink to="/">首页</NuxtLink>
</div>
</template>
页面间导航时的效果:
要为某个页面设置不同的过渡,在页面的 definePageMeta 中设置 pageTransition:
<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'rotate',
},
})
</script>
<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>
跳转到关于页面时会有 3D 旋转效果:
布局过渡
你可以启用布局过渡,为所有布局自动应用过渡动画。
export default defineNuxtConfig({
app: {
layoutTransition: { name: 'layout', mode: 'out-in' },
},
})
要开始添加页面和布局间的过渡,在 app.vue 中添加以下 CSS:
<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>
<template>
<div>
<pre>默认布局</pre>
<slot />
</div>
</template>
<style scoped>
div {
background-color: lightgreen;
}
</style>
<template>
<div>
<pre>橙色布局</pre>
<slot />
</div>
</template>
<style scoped>
div {
background-color: #eebb90;
padding: 20px;
height: 100vh;
}
</style>
<template>
<div>
<h1>首页</h1>
<NuxtLink to="/about">关于页面</NuxtLink>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'orange',
})
</script>
<template>
<div>
<h1>关于页面</h1>
<NuxtLink to="/">首页</NuxtLink>
</div>
</template>
页面间导航时的效果:
和 pageTransition 类似,你可以用 definePageMeta 为页面组件设置自定义 layoutTransition:
<script setup lang="ts">
definePageMeta({
layout: 'orange',
layoutTransition: {
name: 'slide-in',
},
})
</script>
全局配置
你可以在 nuxt.config 中全局自定义默认过渡名称。
pageTransition 和 layoutTransition 都接受 TransitionProps 作为 JSON 可序列化值,你可以传入 name、mode 和其他自定义 CSS 过渡的合法 transition-props。
export default defineNuxtConfig({
app: {
pageTransition: {
name: 'fade',
mode: 'out-in', // 默认
},
layoutTransition: {
name: 'slide',
mode: 'out-in', // 默认
},
},
})
如果你修改了 name 属性,CSS 类名也要相应修改。
要覆盖全局过渡属性,使用 definePageMeta 为单个 Nuxt 页面定义页面或布局过渡,覆盖 nuxt.config 中全局定义的过渡。
<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'bounce',
mode: 'out-in', // 默认
},
})
</script>
禁用过渡
pageTransition 和 layoutTransition 可以为特定路由禁用:
<script setup lang="ts">
definePageMeta({
pageTransition: false,
layoutTransition: false,
})
</script>
或者在 nuxt.config 中全局禁用:
export default defineNuxtConfig({
app: {
pageTransition: false,
layoutTransition: false,
},
})
JavaScript Hooks
对于进阶场景,你可以使用 JavaScript hooks 为 Nuxt 页面创建高度动态和自定义的过渡。
这种方式非常适合配合 GSAP 等 JavaScript 动画库使用。
<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'custom-flip',
mode: 'out-in',
onBeforeEnter: (el) => {
console.log('进入前...')
},
onEnter: (el, done) => {},
onAfterEnter: (el) => {},
},
})
</script>
了解更多 Transition 组件可用的 JavaScript hooks。
动态过渡
要使用条件逻辑应用动态过渡,你可以利用内联中间件为 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>
<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 过渡,跳到上一个时应用 slide-right:
NuxtPage 过渡
当 app.vue 中使用 <NuxtPage /> 时,可以通过 transition prop 配置全局过渡。
<template>
<div>
<NuxtLayout>
<NuxtPage
:transition="{
name: 'bounce',
mode: 'out-in',
}"
/>
</NuxtLayout>
</div>
</template>
注意,这个页面过渡不能被单个页面上的 definePageMeta 覆盖。
View Transitions API(实验性)
Nuxt 内置了 View Transitions API(见 MDN)的实验性实现。这是一种令人兴奋的原生浏览器过渡新方式,它(尤其)能够在不同页面的不相关元素之间做过渡。
你可以在 StackBlitz 上看演示。
在配置文件中启用 experimental.viewTransition 选项:
export default defineNuxtConfig({
experimental: {
viewTransition: true,
},
})
可选值:false、true 或 'always'。
设为 true 时,如果用户浏览器匹配 prefers-reduced-motion: reduce,Nuxt 不会应用过渡(推荐)。设为 always 时,Nuxt 始终应用过渡,由你自己决定是否尊重用户偏好。
默认情况下,所有页面都启用 view transitions,但你可以设置不同的全局默认值。
export default defineNuxtConfig({
app: {
// 全局禁用 view transitions,按页面单独启用
viewTransition: false,
},
})
可以在页面的 definePageMeta 中设置 viewTransition 键来覆盖页面的默认值:
<script setup lang="ts">
definePageMeta({
viewTransition: false,
})
</script>
按页面覆盖 view transitions 只有在你启用了 experimental.viewTransition 选项时才生效。
View Transition 类型 v4.4
View transition 类型允许你根据导航类型应用不同的 CSS 动画。这对于创建不对称过渡很有用(比如前进和后退用不同动画)。
类型设置在 ViewTransition 上,可以在 CSS 中用 :active-view-transition-type() 伪类选择器定位。
你可以在 nuxt.config.ts 中全局设置默认类型:
export default defineNuxtConfig({
app: {
viewTransition: {
enabled: true,
types: ['slide'],
},
},
})
或者用 definePageMeta 按页面配置类型。按页面的类型支持静态数组和动态函数:
<script setup lang="ts">
definePageMeta({
viewTransition: {
enabled: true,
// 涉及此页面的任何过渡都会应用的类型
types: ['slide'],
// 仅导航到此页面时应用的类型
toTypes: ['slide-in'],
// 仅从此页面导航离开时应用的类型
fromTypes: ['slide-out'],
},
})
</script>
你也可以在 definePageMeta 中使用函数作为 types、toTypes 和 fromTypes 的值,根据路由动态确定类型:
<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;
}
}
types、toTypes 和 fromTypes 的函数值只在 definePageMeta 中有效,不能在 nuxt.config.ts 中使用(那里只支持静态 string[])。
page:view-transition:start hook 提供对 ViewTransition 对象的访问,它包含一个 types 属性(ViewTransitionTypeSet),可以在运行时读取或修改:
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:view-transition:start', (transition) => {
// 运行时读取或修改类型
console.log([...transition.types])
})
})
如果你同时在使用 Vue 过渡(比如 pageTransition 和 layoutTransition)来实现和新的 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 函数中做数据获取,目前可能需要重新考虑是否使用这个功能。(按照设计,View Transitions 在执行期间会完全冻结 DOM 更新。)我们正在研究将 View Transition 限制在
<Suspense>resolve 之前的最后时刻,但在此期间,如果你的场景涉及数据获取,请仔细考虑是否采用这个功能。