单页应用没有浏览器自带的整页刷新动画,路由切换像「瞬间切图」,用户体验生硬。本文演示如何借助 Vue Router 的导航守卫 + Vue 的 <transition>
系统,实现方向感滑动动画(前进右滑,后退左滑),并保持嵌套路由、异步组件、keep-alive 等特性零侵入。
一、设计思路:把「方向」抽象成状态
核心只有两步:
- 在路由元信息
meta.index
中定义层级; - 在全局
beforeEach
钩子内比较「到」与「从」的层级,得出direction: 'right' | 'left'
。
js
// router/index.js
router.beforeEach((to, from, next) => {
const fromIndex = from.matched[0]?.meta.index ?? -1;
const toIndex = to.matched[0]?.meta.index ?? -1;
store.routerDirection.direction = toIndex >= fromIndex ? 'right' : 'left';
next();
});
store.routerDirection
是一个极轻量的共享状态,仅存放当前方向,确保任何组件都可读取而不耦合。
二、全局布局:只让主内容区域参与动画
vue
<!-- App.vue -->
<template>
<div>
<!-- 固定导航 -->
<nav class="fixed top-0 w-full h-10 bg-gray-900 text-white flex-center">
<router-link to="/" exact active-class="text-yellow-400">Home</router-link>
<router-link to="/about" active-class="text-yellow-400">About</router-link>
<router-link to="/user" active-class="text-yellow-400">User</router-link>
</nav>
<!-- 占位条,避免内容被固定导航遮挡 -->
<div class="h-10" />
<!-- 主视口:动画只发生在这里 -->
<main class="min-h-screen py-10 lg:w-1/2 mx-auto">
<transition :name="direction">
<router-view />
</transition>
</main>
<!-- 页脚不参与动画,保持静止 -->
<footer class="border-t bg-gray-100 text-center py-10">Footer</footer>
</div>
</template>
关键点:
<transition>
包裹<router-view>
,而非整个页面,避免页脚抖动。:name="direction"
根据状态动态切换right
或left
动画类名。
三、CSS:利用绝对定位实现「滑出 / 滑入」
css
.right-leave-active,
.left-leave-active {
position: absolute;
width: 100%;
transition: 0.5s;
}
.right-leave-active {
transform: translateX(-50%);
opacity: 0;
}
.right-enter {
transform: translateX(50%);
opacity: 0;
}
.right-enter-active {
transition: 0.5s;
}
/* 方向相反,数值对称 */
.left-leave-active {
transform: translateX(50%);
opacity: 0;
}
.left-enter {
transform: translateX(-50%);
opacity: 0;
}
.left-enter-active {
transition: 0.5s;
}
- 绝对定位 让新旧两页同层重叠,避免布局抖动;
- translateX ±50% 创造视差,视觉上更像原生应用;
- opacity 同步淡出淡入,掩盖异步组件加载时的空档。
四、嵌套路由与 keep-alive 的无缝协作
示例中 /user
使用嵌套路由:
js
{
path: '/user',
component: () => import('@/views/user/Layout.vue'),
meta: { index: 2 },
children: [
{ path: '', component: () => import('@/views/user/Profile.vue') },
{ path: 'address', component: () => import('@/views/user/Address.vue') }
]
}
- Layout 外壳不参与动画;子路由切换时
<router-view>
嵌套深度不变,动画依旧生效。 - keep-alive 包裹子路由组件后,切换动画不会触发重新创建,滚动位置与表单状态均可保留。
五、可扩展方向
- 手势返回:监听
touchstart/touchend
,计算滑动方向并手动调用router.back()
; - 多层级深度:给子路由再定义
meta.index
,动画可递归判断; - 微动画库:集成
@vueuse/motion
或framer-motion
做更细腻的共享元素过渡。