在工作中,我们经常会碰到这样的需求,从新闻列表页面进入到新闻详情页,然后再从详情页返回列表页时,需要记住上次列表页面的滚动过的距离,并且再次进入列表页面时,不会从新发起http请求。在vue中,要实现这个需求,相信你一定会想到keep-alive
组件。
首先来解释一下,什么是虚拟任务栈吧。我们的页面中存在着一个记录页面间关系的“虚拟任务栈”,当我们打开站点首页的时候,获取数据&渲染dom,同时会将当前渲染好的页面push
进这个栈中,然后通过首页,跳转的到商品列表页的时候,执行同样的逻辑,执行栈中又保存了列表页的栈帧,并且将其置于栈顶部。如图1所示。当从列表页面返回到首页的时候,列表页这个栈帧被弹出pop
,恢复如图2的效果。
keep-alive
组件主要用于保留组件状态或避免重新渲染。
一、页面跳转状态保存原理
1、所有的组件数据都会被保存下来。
2、需要在组件中创建一个变量(会被keepAlive
保存起来),通过这个变量来记录当前页面的滑动距离。
3、当后退回该页面的时候,使用这个变量来改变当前页面的一个滑动距离。
1、应该在什么时候去改变当前页面的滑动距离?
2、可以在 组件的 activated (keep-aliave 组件被激活的时候被调用)方法中去执行。
二、实现页面跳转状态保存
1、使用keep-alive保存页面状态
<transition :name="transitionName">
<keep-alive :include="virtualTaskStack">
<router-view />
</keep-alive>
</transition>
data(){
return {
virtualTaskStack: {
'Home'
}
}
}
这里的相关代码如页面之间的跳转切换<transition>的实现逻辑已经在另一篇文章《基于Vue实现页面跳转时的“滑动切换效果”》中讲解过了,你可以点击连接查看。
2、借助“虚拟任务栈”实现虚拟任务入栈和出栈
我们可以按照这样的逻辑,在页面前进的过程中,将其存放到虚拟栈中push
,在后退的过程中,将当前页面从栈中弹出pop
。
watch: {
// 监听路由对象,决定使用哪种过渡效果
'$route'(to, from){
// 获取到携带的标记
const routerType = to.params.routerType
if(routerType === 'push'){
// 跳转页面
// 当进入新页面的时候,保存新页面的名称到虚拟任务栈
this.virtualTaskStack.push(to.name)
this.transitionName = 'fold-left'
} else{
// 当执行后退操作的时候,把最后一个页面从任务栈中弹出
this.virtualTaskStack.pop()
// 后退页面
this.transitionName = 'fold-right'
}
}
}
请你重点关注下面两行逻辑。
当进入新页面的时候,保存新页面的名称到虚拟任务栈
// 当进入新页面的时候,保存新页面的名称到虚拟任务栈
this.virtualTaskStack.push(to.name)
当执行后退操作的时候,把最后一个页面从任务栈中弹出
// 当执行后退操作的时候,把最后一个页面从任务栈中弹出
this.virtualTaskStack.pop()
3、应用逻辑(Home组件)
构建模版
<template>
<div class="home"
ref="home"
@scroll="onScroll">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<button @click="toggle">toggle</button>
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
</div>
</template>
设置状态
data(){
return {
scrollTopValue: 0
}
}
模版中存在一个滚动事件@scroll="onScroll"
并设置了ref属性值ref="home"
。
监听滚动,获取页面滚动的高度
onScroll($event){
this.scrollTopValue = $event.target.scrollTop
}
配置跳转标记
toggle(){
this.$router.push({
name: 'About',
params: {
routerType: 'push'
}
})
}
调用activated钩子函数,设置当前页面的滚动高度
/**
* keepAlive 组件被激活时调用
* 去为滑动模块指定滑动距离
* */
activated: function () {
this.$refs.home.scrollTop = this.scrollTopValue;
}
整体的逻辑还是很清晰的,就是通过监听滚动事件,获取页面滚动的高度,在keep
组件被激活的时候执行activated
钩子函数中的逻辑,定位并保存页面的的滚动距离。当下次跳转回来的时候可以记住上次页面滚动过的位置。下面把相关的代码贴出来:
App.vue
<template>
<div id="app">
<transition :name="transitionName">
<keep-alive :include="virtualTaskStack">
<router-view />
</keep-alive>
</transition>
</div>
</template>
<script>
export default{
name: 'App',
data(){
return {
transitionName: 'fold-left',
virtualTaskStack: ['Home']
}
},
watch: {
// 监听路由对象,决定使用哪种过渡效果
'$route'(to, from){
// 获取到携带的标记
const routerType = to.params.routerType
if(routerType === 'push'){
// 跳转页面
// 当进入新页面的时候,保存新页面的名称到虚拟任务栈
this.virtualTaskStack.push(to.name)
this.transitionName = 'fold-left'
} else{
// 当执行后退操作的时候,把最后一个页面从任务栈中弹出
this.virtualTaskStack.pop()
// 后退页面
this.transitionName = 'fold-right'
}
}
}
}
</script>
<style lang="scss">
// push 页面时:新页面的进入动画
.fold-left-enter-active{
animation-name: fold-left-in;
animation-duration: .4s;
}
@keyframes fold-left-in{
0% {
transform: translate(100%, 0);
}
100% {
transform: translate(0,0)
}
}
// push 页面时:原页面的退出动画
.fold-left-leave-active{
animation-name: fold-left-out;
animation-duration: .4s;
}
@keyframes fold-left-out{
0% {
transform: translate(0, 0);
}
100% {
transform: translate(-100%,0)
}
}
// 后退页面时: 即将展示页面的动画
.fold-right-enter-active{
animation-name: fold-right-in;
animation-duration: .4s;
}
@keyframes fold-right-in{
0% {
transform: translate(-100%, 0);
}
100% {
transform: translate(0,0)
}
}
// 后退页面时: 后退页面的动画
.fold-right-leave-active{
animation-name: fold-right-out;
animation-duration: .4s;
}
@keyframes fold-right-out{
0% {
transform: translate(0, 0);
}
100% {
transform: translate(100%,0)
}
}
</style>
Home.vue
<template>
<div class="home"
ref="home"
@scroll="onScroll">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<button @click="toggle">toggle</button>
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
<img src="http://t8.baidu.com/it/u=2247852322,986532796&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1592793437&t=3da58a8314290bf37b99253c355490fd" alt="">
</div>
</template>
<script>
export default {
name: 'Home',
data(){
return {
scrollTopValue: 0
}
},
methods: {
toggle(){
this.$router.push({
name: 'About',
params: {
routerType: 'push'
}
})
},
onScroll($event){
this.scrollTopValue = $event.target.scrollTop
}
},
/**
* keepAlive 组件被激活时调用
* 去为滑动模块指定滑动距离
* */
activated: function () {
this.$refs.home.scrollTop = this.scrollTopValue;
}
};
</script>
<style lang="scss">
.home{
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
overflow-y: auto;
}
img{
width:100%;
}
</style>
About.vue
<template>
<div class="about">
<button @click="toggle">toggle</button>
<div> This page is About.vue</div>
</div>
</template>
<script>
export default{
name: 'About',
methods: {
toggle(){
this.$router.go(-1)
}
}
}
</script>
<style lang="scss">
.about{
position: absolute;
}
</style>
至此,我们分析就结束了,感谢您的阅读,祝你学习愉快!