vue2列表到详情(缓存列表)

375 阅读1分钟

问题描述

在一个列表页下滑到一定位置,点击进入详情页,在返回列表页,此时会 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)