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.keepAlivetrue ,则会将组件进行缓存,因此我们的列表界面的 keepAlive 需要设置为 true

  • 不使用 keep-alive 时,钩子函数执行顺序为:
    beforeRouteEnter --> created --> mounted --> destroyed
  • 使用 keep-alive 缓存组件时,钩子函数执行顺序为:
    beforeRouteEnter --> created --> mounted --> activated --> deactivated
    再次进入缓存的页面,只会触发 beforeRouteEnter -->activated --> deactivatedcreatedmounted 不会再执行。

# 方法一

1、在路由文件 router.js ,给每个路由 meta 添加 scrollTopkeepAlive

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