编程崽

登录

一叶在编程苦海沉沦的扁舟之上,我是那只激情自射的崽

vue路由切换时的动画效果

vue路由切换时的动画效果

使用vue开发页面,经常有需求想要在切换页面时添加一个动画效果,尤其移动端,切换页面时,希望新页面能从右边覆盖过来,返回上一页时,又是前一页从左边覆盖过来。

这里vue的自带组件Transition来调一下。

技术栈:vue3 + ts

官方Transition文档:https://cn.vuejs.org/guide/built-ins/transition

效果:

  • 点击打开下一页时,新页面从右侧覆盖过来,原页面会向左偏移一些,最终被覆盖掉。
  • 返回上一页时,上一个页面从左侧覆盖过来,原页面会向右偏移一些,最终被覆盖掉。
  • 使用router.replace()方法替换当前页面时,不执行动画。

注意点:

  • 需要使用全局状态记录当前是前进、回退页面,因为这个状态的修改和使用,是在不同文件中发生的。
  • 需要自行记录当前打开过的页面历史作为一个数组,以便能判断是否是返回下一页,如果是返回上一页或者替换,需要处理这个数组。
  • 所有的页面文件的 template 标签中的标签,需要使用单独的一个标签包裹,这一点详细在本文档底部说明。

1. 新建一个pinia的store

这个文件的作用存储全局变量,来维护当前的状态,来确定当前是否是不使用动画、是前进页面、是回退页面。

新建文件 src/stores/pageTransition.ts 内容:

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
		},
	},
})

2. 修改路由配置文件

修改路由配置文件,这个文件一般是 src/router/index.ts

这个文件中:

  1. 需要创建 routeStack 数组,记录的已打开过页面的路由栈。

  2. 创建 isReplace 变量,用来记录「当前是否是替换页面」,然后重写 router.replace 方法,在这里面把 isReplace 改为 true,然后再真的去调用 vue-router 的替换页面的 Api。

  3. router.beforeEach 路由守卫中添加代码,主要功能是:判断当前是替换页面、返回上一页还是正常跳新页面,然后修改全局变量 src/stores/pageTransition.ts 中的值。

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

3. App.vue中配置动画

使用 Transition 组件,并在 style 中添加动画。

  • Transition 组件的 name 属性,需要取全局状态的记录值,值可能为 '' | 'prev' | 'next'
  • 添加 Transition 组件添加 @after-leave 事件监听,也就是切换页面完成后,需要重置全局状态的记录值。
  • Transition 的执行动画时的名称,是基于 name 属性拼接的,所以在 style 中配置动画时,需要分别配置好。
sh 复制代码
<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>

至此,完成。

补充:页面文件的 template 标签中的写法

本来,vue3的组件中是可以这么写的:

sh 复制代码
<template>
  <div>第一行</div>
  <div>第二行</div>
  一些其他文本
</template>

但如果要使用这个页面切换效果,就不可以了,必须使用一个标签把他们都包裹起来。

因为上面第3步中的style中使用的那个类名,是直接添加到这个页面组件上面,这个组件的html代码的顶层必须是一个可以承接类名class属性的标签。

修改后的写法:

sh 复制代码
<template>
  <div>
    <div>第一行</div>
    <div>第二行</div>
    一些其他文本
  </div>
</template>