问题描述
在一个列表页下滑到一定位置,点击进入详情页,在返回列表页,此时会 scroll 到顶部,并重新渲染列表页。
我们希望返回列表页面的时候,还是之前访问列表页下滑的位置。
原因
路由切换时,页面会 scroll 到顶部,并重新渲染。所有的 SPA 都会有这个问题,例如: Vue、React等 。
解决方案
- 在列表页缓存数据 和
scrollTop
。我们根据路由$route.meta.keepAlive
来使用keep-alive
进行缓存 - 在列表组件的
beforeRouteEnter
中,判断from.name
,要是来自详情页,在to.meta.canKeep
标识为true
- 在
activated
中, 判断this.$route.meta.canKeep
要是为true
, 就scrollTo(scrollTop)
代码
查看源代码请点击这里
/src/App.vue
<template>
<div style="height: 100%; overflow: auto;">
<keep-alive>
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" />
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
html,body{padding: 0; margin: 0; height: 100%; overflow: auto;}
</style>
/src/pages/list.vue
<template>
<div class="page-box">
<nav-bar title="列表页"></nav-bar>
<div class="list-box" ref="containerRef">
<div
class="term"
v-for="i in 1000"
:key="i"
@click="$router.push('detail')"
>
{{ i }} 当前是列表页,点击进入详情页。
</div>
</div>
<back-top @click="toUp" v-if="scrollTo > 200"></back-top>
</div>
</template>
<script>
import navBar from '../components/nav-bar'
import backTop from '../components/back-top'
export default {
name: 'list',
components: {
navBar,
backTop,
},
data() {
return {
scrollTo: 0,
}
},
beforeRouteEnter(to, from, next) {
if (from.name === 'detail') {
to.meta.canKeep = true;
} else {
to.meta.canKeep = false;
}
next();
},
computed: {
containerEl() {
return this.$refs.containerRef
}
},
mounted() {
this.$nextTick(()=> {
this.containerEl.onscroll = ()=> { // 记录滚动距离
this.scrollTo = this.containerEl.scrollTop
}
})
},
activated() {
// 窗口滚动
// if (this.$route.meta.canKeep) {
// window.scrollTo({
// left: 0,
// top: this.scrollTo,
// })
// } else {
// window.scrollTo({
// left: 0,
// top: 0,
// })
// }
// 元素区域滚动
if (this.$route.meta.canKeep) {
this.containerEl.scrollTo({
left: 0,
top: this.scrollTo,
})
} else {
this.containerEl.scrollTo({
left: 0,
top: 0,
})
this.scrollTo = 0
}
},
methods: {
toUp() {
this.containerEl.scrollTo({
left: 0,
top: 0,
})
}
}
}
</script>
<style scoped>
.page-box{flex: 1; display: flex; flex-direction: column; height: 100%; overflow: auto; box-sizing: border-box;}
.list-box{flex: 1; overflow: auto;}
.list-box .term{padding: 10px; border-bottom: 1px solid #ccc; cursor: pointer;}
</style>
/src/pages/detail.vue
<template>
<div class="page-box">
<nav-bar title="详情页"></nav-bar>
<div class="list-box" ref="containerRef">
<div class="term" v-for="i in 1000" :key="i">
{{ i }} 当前是详情页
</div>
</div>
<back-top @click="toUp" v-if="scrollTo > 200"></back-top>
</div>
</template>
<script>
import navBar from '../components/nav-bar'
import backTop from '../components/back-top'
export default {
name: 'detail',
components: {
navBar,
backTop,
},
data() {
return {
scrollTo: 0,
}
},
computed: {
containerEl() {
return this.$refs.containerRef
}
},
mounted() {
this.$nextTick(()=> {
this.containerEl.onscroll = ()=> { // 记录滚动距离
this.scrollTo = this.containerEl.scrollTop
}
})
},
methods: {
toUp() {
this.containerEl.scrollTo({
left: 0,
top: 0,
})
}
}
}
</script>
<style scoped>
.page-box{flex: 1; display: flex; flex-direction: column; height: 100%; overflow: auto; box-sizing: border-box;}
.list-box{flex: 1; overflow: auto;}
.list-box .term{padding: 10px; border-bottom: 1px solid #ccc; cursor: pointer;}
</style>
/src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: ()=> import('src/pages/home'),
meta: {
keepAlive: true
}
},
{
path: '/list',
name: 'list',
component: ()=> import('src/pages/list'),
meta: {
keepAlive: true
}
},
{
path: '/detail',
name: 'detail',
component: ()=> import('src/pages/detail'),
}
]
})
总结
- 根据路由来使用
keep-alive
进行缓存数据和scrollTo
- 在组件
beforeRouteEnter
判断是前进还是后退 - 在组件
activated
进行scrollTo(scrollTo)