Vue Router 路由切换动效
1. 核心思想:利用 <transition> 组件
Vue Router 的路由切换本质上是组件的销毁和创建。我们可以利用 Vue 内置的 <transition> 组件来包裹 <router-view>,从而在路由组件切换时自动应用过渡效果。
这并不是新知识,而是将 Vue 的过渡动画知识点应用于路由场景。
html
<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>2. 基础实现:单向滑动动画
我们可以实现一个常见的动效:旧组件向左滑出,新组件从右侧滑入。
2.1. 包裹 <router-view> 并命名
在根组件 (如 App.vue) 中,用 <transition> 包裹 <router-view>,并为其提供一个 name,例如 left。
html
<router-view v-slot="{ Component }">
<transition name="left">
<component :is="Component" />
</transition>
</router-view>2.2. 定义 CSS 过渡类
根据 name="left",我们需要定义相应的 CSS 类来描述动画过程。Vue 会在过渡的不同阶段自动添加/删除这些类。
.left-enter-from: 进入动画的起始状态。.left-leave-to: 离开动画的结束状态。.left-enter-active,.left-leave-active: 定义进入和离开动画的持续时间、延迟和曲线函数。
vue
<style scoped>
/* 进入动画的起始状态:组件在屏幕右侧外边,完全透明 */
.left-enter-from {
transform: translateX(100%);
opacity: 0;
}
/* 离开动画的结束状态:组件移动到屏幕左侧外边,完全透明 */
.left-leave-to {
transform: translateX(-100%);
opacity: 0;
}
/* 进入和离开动画的过渡效果:持续 0.5 秒 */
.left-enter-active,
.left-leave-active {
transition: all 0.5s ease;
}
</style>3. 问题修复:解决组件共存时的跳动问题
3.1. 问题分析
在过渡期间,新旧两个组件会同时存在于 DOM 中。由于它们都是块级元素,会遵循正常的文档流布局,导致新组件被“挤”到旧组件的下方,动画结束后再“跳”回正确位置。
3.2. 解决方案:绝对定位
为了解决跳动问题,我们可以给 正在离开的组件 添加 position: absolute。这样它就会脱离文档流,不再影响新进入组件的布局。
css
<style scoped>
/* ... 其他样式 ... */
/* 离开动画激活时,让旧组件脱离文档流 */
.left-leave-active {
transition: all 0.5s ease;
position: absolute; /* 关键!脱离文档流 */
width: 100%; /* 保持宽度 */
left: 0; /* 保持水平位置 */
}
</style>4. 进阶实现:动态过渡方向 (前进/后退)
在实际应用中,我们希望根据导航的层级关系决定动画方向。例如:
Home->About(前进):向左滑动。About->Home(后退):向右滑动。
这就要求 <transition> 的 name 属性是动态的。
4.1. 实现思路
整个逻辑链条如下:
- 路由 (Router):通过导航守卫
beforeEach比较目标路由和来源路由的层级。 - 判断方向:根据层级比较结果,确定应该是
left还是right动画。 - 状态仓库 (Store):将判断出的方向('left'/'right')存储在一个全局共享的状态中(如 Vuex)。
- 组件 (Component):
<transition>组件的name属性动态绑定到仓库中的方向数据。 - 渲染:当仓库数据变化时,组件自动重新渲染,应用正确的
name,从而触发对应的 CSS 动画。
4.2. 步骤 1: 为路由添加元数据 (meta)
在路由配置中,为每个路由添加一个 meta 对象,并定义一个 index 来表示其层级或顺序。
javascript
// router/index.js
const routes = [
{
path: '/home',
name: 'Home',
component: Home,
meta: { index: 0 } // 首页,层级为 0
},
{
path: '/about',
name: 'About',
component: About,
meta: { index: 1 } // 关于页,层级为 1
},
{
path: '/user',
name: 'User',
component: User,
meta: { index: 2 } // 用户页,层级为 2
}
];4.3. 步骤 2: 创建状态仓库 (Store) 管理方向
使用 Vuex 或其他状态管理工具来存储全局的过渡方向。
javascript
// store/index.js (以 Vuex 为例)
import { createStore } from 'vuex';
export default createStore({
state: {
// 默认过渡方向
transitionName: 'left'
},
mutations: {
// 修改过渡方向的方法
setTransitionName(state, name) {
state.transitionName = name;
}
},
actions: {},
modules: {}
});4.4. 步骤 3: 使用导航守卫 (beforeEach) 判断方向
在路由配置文件中,添加一个全局前置守卫,根据 meta.index 判断导航方向,并提交 mutation 更改仓库状态。
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import store from '../store'; // 引入仓库
// ... routes 定义 ...
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});
router.beforeEach((to, from, next) => {
// 只有当 to 和 from 都有 meta.index 时才进行比较
if (to.meta.index && from.meta.index) {
// from.index < to.index,判定为前进,使用 left 动画
// from.index > to.index,判定为后退,使用 right 动画
const transitionName = to.meta.index < from.meta.index ? 'right' : 'left';
store.commit('setTransitionName', transitionName);
}
next();
});
export default router;4.5. 步骤 4: 动态绑定 <transition> 的 name
最后,在 App.vue 中,将 <transition> 的 name 属性与仓库中的 transitionName 状态进行绑定。同时,准备好 right 过渡方向的 CSS 类。
vue
<template>
<router-view v-slot="{ Component }">
<transition :name="$store.state.transitionName">
<component :is="Component" />
</transition>
</router-view>
</template>
<style scoped>
/* 向左滑动 (前进) */
.left-enter-from { transform: translateX(100%); opacity: 0; }
.left-leave-to { transform: translateX(-100%); opacity: 0; }
.left-enter-active,
.left-leave-active {
transition: all 0.5s ease;
position: absolute;
width: 100%;
left: 0;
}
/* 向右滑动 (后退) */
.right-enter-from { transform: translateX(-100%); opacity: 0; }
.right-leave-to { transform: translateX(100%); opacity: 0; }
.right-enter-active,
.right-leave-active {
transition: all 0.5s ease;
position: absolute;
width: 100%;
left: 0;
}
</style>