上一篇详细阐述了v1版本的实现思路和代码:h5页面路由切换动画-左右横滑效果(v1)
后来在实践过程中发现v1存在的诸多问题,已经做了重构,所以这里分享一下新的实现方式,且称为v2版本吧,核心思路还是一样的。
一、回顾与思考
v1版本通过vuex存储页面历史和页面方向,通过beforeEach全局路由导航守卫来获取要跳转的路由信息,对比判断页面方向。
后来想了一下:
- 为什么使用vuex存储而不用普通js模块存储,是因为vuex里的页面方向数据的变化能触发响应式,使切换动画能根据页面方向即时变换;
- 而vuex里这块数据就只有页面切换模块用到,是否可以移入封装的页面切换组件 PageSlide 里;
如果移进去那组件内的 handlePageDirection 方法就没办法在路由导航守卫 beforeEach 里调用了,而调用的目的就只是传递下一页的路由信息; - 那是否有其他方式来获取下一页路由,有,watch监听 $route 即可,因为路由出口是要被包裹在这个组件里的。
那么改造后一个组件就实现了所有代码逻辑。
二、其他优化点
1、精简路由栈数据
v1里路由栈routeStack数据存储的是所有路由route的完整数据,而当前功能其实只用到path这一个数据而已,那就只存储path,同时也减去了 JSON.parse(JSON.stringify(route)) 的深克隆操作。
pushRouteStack (route) {
const { path } = route
this.routeStack.push({ path })
},
2、支持页面刷新
v1里存储vuex在页面刷新后路由栈routeStack数据会初始化清空,所以这次把routeStack数据同步存储在sessionStorage里,页面刷新后从sessionStorage读取出来。
created () {
this.routeStack = this.getSessionRouteStack()
},
pushRouteStack (route) {
const { path } = route
this.routeStack.push({ path })
this.setSessionRouteStack()
},
popRouteStack () {
this.routeStack.pop()
this.setSessionRouteStack()
},
3、兼容vue3.0
大概瞄了一眼,vue3.0不兼容的部分应该就只有transition类名吧: class-enter 修改为 class-enter-from ; class-leave 修改为 class-leave-from 。
那就全写上:
.slide-left-enter,
.slide-left-enter-from,
.slide-right-leave-active {
opacity: 0;
transform: translate(@distanceX, 0);
}
.slide-left-leave-active,
.slide-right-enter,
.slide-right-enter-from {
opacity: 0;
transform: translate(-@distanceX, 0);
}
更多vue3.0升级可以参考:项目升级vue3.0经验总结
4、重构页面方向判断逻辑
这一版对页面前进后退的判断逻辑进行了重构,考虑了更多细节场景的处理,路由栈初始时默认存放首页路由:
handleRouteChange (to) {
const len = this.routeStack.length
const currentRoute = this.routeStack[len - 1]
if (currentRoute.path !== to.path) {
if (len === 1) {
this.setPageForward(to)
} else {
const lastRoute = this.routeStack[len - 2]
if (lastRoute.path === to.path) {
this.setPageBack(currentRoute)
} else {
if (to.path === this.indexPagePath) {
this.setDirectionName('slide-right')
this.resetRouteStack()
} else {
this.setPageForward(to)
}
}
}
}
},
三、完整代码封装
1、封装后的组件 PageSlide
<template>
<transition :name="directionName">
<slot></slot>
</transition>
</template>
<script>
export default {
name: 'PageSlide',
props: {
indexPagePath: {
type: String,
default: '/',
},
},
data () {
return {
directionName: '',
routeStack: [],
}
},
watch: {
'$route' (val) {
this.handleRouteChange(val)
}
},
created () {
this.routeStack = this.getSessionRouteStack() || [{ path: this.indexPagePath }]
},
methods: {
handleRouteChange (to) {
const len = this.routeStack.length
const currentRoute = this.routeStack[len - 1]
if (currentRoute.path !== to.path) {
if (len === 1) {
this.setPageForward(to)
} else {
const lastRoute = this.routeStack[len - 2]
if (lastRoute.path === to.path) {
this.setPageBack(currentRoute)
} else {
if (to.path === this.indexPagePath) {
this.setDirectionName('slide-right')
this.resetRouteStack()
} else {
this.setPageForward(to)
}
}
}
}
},
setPageForward (route) {
this.setDirectionName('slide-left')
this.pushRouteStack(route)
},
setPageBack () {
this.setDirectionName('slide-right')
this.popRouteStack()
},
setDirectionName (name) {
this.directionName = name
},
resetRouteStack () {
this.routeStack = [{ path: this.indexPagePath }]
this.setSessionRouteStack()
},
pushRouteStack (route) {
const { path } = route
this.routeStack.push({ path })
this.setSessionRouteStack()
},
popRouteStack () {
this.routeStack.pop()
this.setSessionRouteStack()
},
setSessionRouteStack () {
sessionStorage.setItem('routeStack', JSON.stringify(this.routeStack))
},
getSessionRouteStack () {
const str = sessionStorage.getItem('routeStack')
if (!str) {
return null
}
let val = []
try {
val = JSON.parse(str)
} catch (error) {
console.warn('parse routeStack wrong')
}
return val
},
}
}
</script>
<style lang="less" scoped>
@distanceX: 100%;
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
position: absolute;
width: 100%;
transition: all 0.3s cubic-bezier(0.55, 0, 0.1, 1);
}
.slide-left-enter,
.slide-right-leave-active {
opacity: 0;
transform: translate(@distanceX, 0);
}
.slide-left-leave-active,
.slide-right-enter {
opacity: 0;
transform: translate(-@distanceX, 0);
}
</style>
2、App.vue 引用
<template>
<div id="app">
<PageSlide indexPagePath="/home">
<router-view />
</PageSlide>
</div>
</template>
<script>
import PageSlidefrom '@/components/PageSlide'
export default {
name: 'App',
components: {
PageSlide,
},
}
</script>
- indexPagePath 参数是项目首页路由的path,换成你自己项目的path路径。
四、react实现指引
不管是v1还是v2、vue还是react,核心思想不变,即: 存储路由访问历史数据,获取下一页路由数据,进行对比判断出页面方向。
react版的实现方式上主要需要解决以下问题:
- 1、过渡动画:可以通过 react-transition-group 实现。
- 2、获取下一页路由数据:可以通过 react-router 的 withRouter 来实现。
- 3、监听路由的变化:
- 配合上述 withRouter ,当 props.location变化时, componentWillReceiveProps 钩子就会执行,可作为监听函数;
import { withRouter } from 'react-router-dom'
class Main extends Component {
constructor (props) {
super(props)
this.state = {}
}
componentWillReceiveProps (nextProps) {
console.log(nextProps.location)
}
render() {
return (
<div>demo</div>
)
}
}
export default withRouter(Main)
- hooks语法就更简单了,useEffect 直接就能响应路由的变化。
const Demo = function (props) {
useEffect(() => {
console.log(props.location)
}, [props.location])
}
剩下的逻辑就和vue大同小异了,自己实现吧。
|