vant list 组件滚动保留滚动条位置,需结合 keepAlive 使用
# 需求
1. 现有一个列表界面 page1,列表详情界面 page2。
2. 先从列表界面 page1 进入到列表详情界面 page2,然后从 page2 回到 page1 之后,列表界面 page1 的位置不刷新(即回到原来的浏览位置)
# 实现方法
1、保存位置的前提是用的 keepAlive 组件来做缓存, app.vue 代码
| <template> | |
|   <div id="app"> | |
|     <keep-alive> | |
|       <router-view v-if='$route.meta.keepAlive'/> | |
|     </keep-alive> | |
|     <router-view  v-if='!$route.meta.keepAlive'/> | |
|   </div> | |
| </template> | 
通过 V-if 进行判断,如果前面路由配置的 $route.meta.keepAlive 为 true ,则会将组件进行缓存,因此我们的列表界面的 keepAlive 需要设置为 true 。
- 不使用 keep-alive时,钩子函数执行顺序为:beforeRouteEnter --> created --> mounted --> destroyed
- 使用 keep-alive缓存组件时,钩子函数执行顺序为:beforeRouteEnter --> created --> mounted --> activated --> deactivated
 再次进入缓存的页面,只会触发beforeRouteEnter -->activated --> deactivated。created和mounted不会再执行。
# 方法一
1、在路由文件 router.js ,给每个路由 meta 添加 scrollTop 和 keepAlive
| { | |
| path: '/home', | |
| name: 'home', | |
| component: resolve => require(['@/views/home/index.vue'], resolve), | |
| meta: { | |
| title: '首页', | |
| index: 1, | |
| keepAlive: true, | |
| scrollTop: 0 | |
|       } | |
| }, | |
|     { | |
| path: '/classify', | |
| name: 'classify', | |
| component: resolve => require(['@/views/classify/index.vue'], resolve), | |
| meta: { | |
| title: '分类', | |
| index: 1, | |
| keepAlive: true, | |
| scrollTop: 0 | |
|       } | |
| }, | |
|     { | |
| path: '/shopping', | |
| name: 'shopping', | |
| component: resolve => require(['@/views/shopping/index.vue'], resolve), | |
| meta: { | |
| title: '购物车', | |
| index: 1, | |
| keepAlive: true, | |
| scrollTop: 0 | |
|       } | |
| }, | |
|     { | |
| path: '/detail', | |
| name: 'detail', | |
| component: resolve => require(['@/views/detail/index.vue'], resolve), | |
| meta: { | |
| title: '详情', | |
| index: 2, | |
|         // keepAlive: true, | |
|         // scrollTop: 0 | |
|       } | |
| }, | 
2、然后在 main.js , 记录滚动条的位置
| router.beforeEach((to, from, next) => { | |
| if (from.meta.keepAlive) { | |
| const $wrapper = document.querySelector('.app-wrapper'); // 列表的外层容器 注意找到滚动的盒子 | |
| const scrollTop = $wrapper ? $wrapper.scrollTop : 0; | |
| console.log('scrollTop=', scrollTop) | |
| from.meta.scrollTop = scrollTop; | |
|   } | |
| next(); | |
| }); | 
3、最后在需要记录保留滚动条位置的地方获取通过 activated(这个函数每次进入页面都会执行,只有结合使用 keepAlive 组件才有效)来获取 scrollTop
| activated () { | |
| const scrollTop = this.$route.meta.scrollTop; | |
| const $wrapper = document.querySelector('.app-wrapper'); | |
| if (scrollTop && $wrapper) { | |
| $wrapper.scrollTop = scrollTop; | |
|     } | |
| }, | 
比如缓存了某些页面也不想随之滚动,则把 scrollTop 置 0 即可;
| activated() { | |
| const $wrapper = document.querySelector(".app-wrapper"); | |
| $wrapper.scrollTop = 0; | |
| }, | 
注意,页面滚动的话,其他页面有滚动条的也会随之滚动,可以对其他页面里面处理,或者判断是否从详情页到列表页来判断是否缓存位置,如果不是,则回到顶部,但是注意路由钩子函数 this 的使用.
issue
- 当 route 开启 reactive 缓存时,van-tabs 的转场动画 animated 属性会导致 van-tab 永远指向 name="0" 
- 如果不定义 name,则使用 keep-alive 会直接去取文件名,也就是 index,发生错误,keep-alive 缓存无效。 
# 方法二
# 一、先缓存列表界面
1. 先在路由管理文件 index.js 中添加 meta 属性
| { | |
| path: '/datadetail', | |
|     component: DataDetail | |
| }, | |
| { | |
| path: '/datalist', | |
| component: DataList, | |
|     // 设置 keepAlive:true--- 说明此组件需要进行数据缓存 | |
| meta: { | |
| keepAlive: true | |
|     } | |
| }, | 
# 二、获取下拉列表的位置
1. 先在 page1.vue 列表详情组件中找到下拉列表的 div 并设置 ref 属性
| <div class="wrapper" ref="wrapper"> | |
| <div class="title">我是标题</div> | |
| <van-pull-refresh v-model="isRefresh" @refresh="onRefresh"> | |
| <van-list class="list" v-model="loadingMore" :finished="finished" finished-text="没有更多了" @load="onLoadMore"> | |
| <div class="item-wrapper" v-for="item in list" :key="item.id" @click="clickItem(item)"> | |
| <div class="item"><!--swig0--></div> | |
| </div> | |
| </van-list> | |
| </van-pull-refresh> | |
| </div> | 
在上面的下拉列表中设置 ref="wrapper" 的属性
2. 因为使用了 keep-alive ,页面被缓存起来了,所以 data 里的数据不会丢失,可以在 data 中声明一个变量 scroll 来存储 scrollTop 的值。
| data() { | |
| return { | |
| scroll: 0,// 存储 `scrollTop` 的值 | |
|     	} | |
| }, | |
| 	// 离开路由之前执行的函数 | |
| beforeRouteLeave(to, from, next) {...}, | 
3. 然后再 page1.vue 的页面中添加一个钩子函数 beforeRouteLeave(to, from, next)
| // 离开路由之前执行的函数 | |
| beforeRouteLeave(to, from, next) { | |
|     // 如果在 window 中出现的滚动条 | |
|     // this.scroll = window.scrollTop; | |
|     //  如果在某个指的元素中出现的滚动条 就在该素中添加 ref 属性(例如上面的 div 设置 ref="wrapper") | |
| this.scroll = this.$refs.wrapper.scrollTop; | |
| next() | |
| }, | 
# 三、获取并设置 scrollTop
通过 beforeRouteLeave(to, from, next) 来获取的列表位置值,并将位置值存储到 scroll 中,从 page2 页面返回到列表页面 page1 时,获取前面缓存的列表高度 scroll 值,并赋值给 scrollTop ,从而达到返回列表时位置不变,只需要再 activated 钩子函数中设置 scrollTop ,就可实现需求。
| data() { | |
| return { | |
| scroll: 0,// 存储 `scrollTop` 的值 | |
|         } | |
| }, | |
|     // 离开路由之前执行的函数 | |
| beforeRouteLeave(to, from, next) {...}, | |
|     // 这一步就能实现需求 | |
| activated() { | |
| this.$refs.wrapper.scrollTop = this.scroll | |
|     } | 
当然也有的说是,将 scroll 赋值的时候,直接赋值在进入路由之前执行的钩子函数中 beforeRouteEnter(to, from, next) ,但是这个我没有实现,也可以参考一下:
| data() { | |
| return { | |
| scroll: 0,// 存储 `scrollTop` 的值 | |
|         } | |
| }, | |
|     // 离开路由之前执行的函数 | |
| beforeRouteLeave(to, from, next) {...}, | |
|     // 进入路由之前执行的函数 | |
| beforeRouteEnter(to, from, next) { | |
| next(vm => { | |
|             // 如果在 window 中出现的滚动条 | |
|             // window.scrollTop = vm.scroll; | |
|             // 如果在某个指的元素中出现的滚动条 就在该素中添加 ref 属性,如:ref="listBox" | |
| vm.$refs.listBox.scrollTop = vm.scroll | |
| }) | |
|    } | 
