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的使用.

+++primary 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">{{item}}</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
        })
   }