使用vue开发页面,经常有需求想要在切换页面时添加一个动画效果,尤其移动端,切换页面时,希望新页面能从右边覆盖过来,返回上一页时,又是前一页从左边覆盖过来。
这里vue的自带组件Transition来调一下。
技术栈:vue3 + ts
官方Transition文档:https://cn.vuejs.org/guide/built-ins/transition
效果:
注意点:
template
标签中的标签,需要使用单独的一个标签包裹,这一点详细在本文档底部说明。这个文件的作用存储全局变量,来维护当前的状态,来确定当前是否是不使用动画、是前进页面、是回退页面。
新建文件 src/stores/pageTransition.ts
内容:
import { defineStore } from 'pinia';
type Type = '' | 'prev' | 'next'
export const usePageTransition = defineStore('usePageTransition', {
state: () => ({
changeType: '' as Type, // 页面跳转的方向,是返回,还是前进,对应不同的动画
}),
getters: {
// 获取Transition的name
pageTransitionName(state) {
if (state.changeType === 'prev') return 'page-prev'
else if (state.changeType === 'next') return 'page-next'
},
},
actions: {
changePageType(newVal: Type) {
this.changeType = newVal
},
},
})
修改路由配置文件,这个文件一般是 src/router/index.ts
。
这个文件中:
需要创建 routeStack
数组,记录的已打开过页面的路由栈。
创建 isReplace
变量,用来记录「当前是否是替换页面」,然后重写 router.replace
方法,在这里面把 isReplace
改为 true,然后再真的去调用 vue-router
的替换页面的 Api。
在 router.beforeEach
路由守卫中添加代码,主要功能是:判断当前是替换页面、返回上一页还是正常跳新页面,然后修改全局变量 src/stores/pageTransition.ts
中的值。
import { usePageTransition } from '@/stores/pageTransition';
import * as VueRouter from 'vue-router'
let router = VueRouter.createRouter({
// history: VueRouter.createWebHashHistory(),
history: VueRouter.createWebHistory(),
routes: [
// 路由信息们
],
})
// 记录的已打开过页面的路由栈
let routeStack: VueRouter.RouteLocationNormalizedGeneric[] = []
let isReplace = false // 是否是替换页面跳转的
// 扩展 Vue Router 实例,以便在每次调用 replace 方法时添加一个标志
const originalReplace = router.replace.bind(router);
router.replace = ((location: string) => {
isReplace = true
originalReplace(location)
}) as typeof router.replace
router.beforeEach((to, from) => {
usePageTransition().changePageType('')
// 是替换页面
if (isReplace) {
routeStack.pop()
routeStack.push(to)
isReplace = false
} else if (routeStack[routeStack.length - 2]?.path === to.path) {
// 发现实际是返回上一页
routeStack.splice(routeStack.length - 2, 2)
routeStack.push(to)
// 设置路由动画为返回
usePageTransition().changePageType('prev')
} else {
routeStack.push(to)
// 设置路由动画为前进
usePageTransition().changePageType('next')
}
return true
})
export default router
使用 Transition
组件,并在 style
中添加动画。
Transition
组件的 name
属性,需要取全局状态的记录值,值可能为 '' | 'prev' | 'next'
。Transition
组件添加 @after-leave
事件监听,也就是切换页面完成后,需要重置全局状态的记录值。Transition
的执行动画时的名称,是基于 name
属性拼接的,所以在 style
中配置动画时,需要分别配置好。<template>
<router-view #default="{ Component }">
<Transition @after-leave="usePageTransition().changePageType('')" :name="usePageTransition().pageTransitionName">
<keep-alive>
<component :is="Component" />
</keep-alive>
</Transition>
</router-view>
{{ usePageTransition().pageTransitionName }}
</template>
<script lang="ts" setup>
import { usePageTransition } from '@/stores/pageTransition';
</script>
<style lang="scss" scoped>
.page-prev-enter-active, .page-prev-leave-active,
.page-next-enter-active, .page-next-leave-active {
transition: all 1s;
position: fixed;
overflow: hidden;
width: 100vw;
left: 0;
top: 0;
background: white;
}
.page-prev-enter-active, .page-next-enter-active {
z-index: 2;
}
.page-prev-leave-active, .page-next-leave-active {
z-index: 1;
}
.page-prev-enter-from {
transform: translateX(-100vw);
}
.page-next-enter-from {
transform: translateX(100vw);
}
.page-next-leave-to {
transform: translateX(-50vw);
}
.page-prev-leave-to {
transform: translateX(50vw);
}
</style>
至此,完成。
本来,vue3的组件中是可以这么写的:
<template>
<div>第一行</div>
<div>第二行</div>
一些其他文本
</template>
但如果要使用这个页面切换效果,就不可以了,必须使用一个标签把他们都包裹起来。
因为上面第3步中的style中使用的那个类名,是直接添加到这个页面组件上面,这个组件的html代码的顶层必须是一个可以承接类名class属性的标签。
修改后的写法:
<template>
<div>
<div>第一行</div>
<div>第二行</div>
一些其他文本
</div>
</template>