Vue 返回記住滾動條位置詳解

最近用 Vue 做移動端頁面遇到一個問題,從列表頁進入詳情頁,再返回到列表頁,不管之前滾動到哪里,每次返回時都跳到列表最頂部。

這樣體驗肯定不好,期望的應(yīng)該是記住滾動條的位置,每次返回還是在原來的位置上,便于繼續(xù)瀏覽。

于是在網(wǎng)上搜解決方法,搜了一大圈看了 n 篇文章,都沒有說清楚。起碼我是沒有通過看一篇文章而完美解決,所以決定寫一篇詳細的親測可行的解決方案。

一共分三步:

  • 給 router-view 添加 keep-alive
  • 獲取并存儲當前 scrollTop
  • 返回時取出并設(shè)置 scrollTop

100 多位經(jīng)驗豐富的開發(fā)者參與,在 Github 上獲得了 1000+star 的全棧全平臺開源項目想了解或參與嗎?
項目地址:https://github.com/cachecats/coderiver

一、給 router-view 添加 keep-alive

先貼出 keep-alive 官方文檔,不熟悉的可以先看看文檔。

<keep-alive> 包裹動態(tài)組件時,會緩存不活動的組件實例,而不是銷毀它們。所以在由詳情頁返回列表頁時,不讓列表頁刷新。

當組件在 <keep-alive> 內(nèi)被切換,它的 activateddeactivated 這兩個生命周期鉤子函數(shù)將會被對應(yīng)執(zhí)行。

設(shè)置 scrollTop 時是在 activated 方法里,有些文章說獲取 scrollTopdeactivated 方法里,但經(jīng)過測試,在 deactivated 方法里并不能準確的獲取 scrollTop 值,每次都是 0。具體原因暫時不深究,先解決問題。所以把獲取 scrollTop 值放在 item 的點擊事件函數(shù)里執(zhí)行。

二、獲取并存儲當前 scrollTop

頁面布局如下:

image

整個頁面是一個 <rounter-view> ,下面又分了兩個 tab,我們列表頁是一個組件,位于 title 和 導(dǎo)航欄之間的區(qū)域。

布局代碼:

<div class="wrapper" ref="wrapper">
    <div class="title">我是標題</div>
    <van-pull-refresh v-model="isRefresh" @refresh="onRefresh" ref="pullRefresh">
      <van-list
          ref="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)" ref="item">
          <div class="item">{{item}}</div>
        </div>
      </van-list>
    </van-pull-refresh>
  </div>

用到了 Vant-ui 的下拉刷新和上拉加載更多組件。

可以看到我一共給了四個 ref ,分別是最外層的 ref="list" ,下拉刷新組件 van-pull-refreshref="pullRefresh",列表組件 van-listref="list",和每個 item 的 ref="item"。

為什么給出這么多呢?因為這里有個大坑,也是我一直卡住的地方。

我們知道獲取滾動位置是用 scrollTop 這個屬性,下面我們就依次打印出這幾個元素的 scrollTop 。

clickItem(item) {
    let wrapperScrollTop = this.$refs.wrapper.scrollTop;
    let pullRefreshScrollTop = this.$refs.pullRefresh.scrollTop;
    let listScrollTop = this.$refs.list.scrollTop;
    let itemScrollTop = this.$refs.item.scrollTop;

    console.log('wrapperScrollTop', wrapperScrollTop);
    console.log('pullRefreshScrollTop', pullRefreshScrollTop);
    console.log('listScrollTop', listScrollTop);
    console.log('itemScrollTop', itemScrollTop);

    this.$router.push({name: "detail", params: {data: item}})
},

放到 item 的點擊事件里觸發(fā)。得到的日志如下:

image

WTF?只有第一個 wrapperScrollTop 有值,其他的都 undefined !

我也不知道為啥,之前一直是獲取后三者的 scrollTop ,一直獲取不到,糾結(jié)了好久。為什么其他三個獲取不到我現(xiàn)在還沒整明白,知道原因的大佬可以指點一下。

知道了該獲取哪一個元素的 scrollTop 就簡單了,得到值只需存儲起來即可。

因為使用了 keep-alive,頁面被緩存起來了,所以 data 里的數(shù)據(jù)不會丟失,可以在 data 中聲明一個變量 scroll 存儲 scrollTop 的值。也可以使用 Vuex。

修改下 clickItem(item) 的代碼,將 scrollTop 的值存儲起來。

clickItem(item) {
    let wrapperScrollTop = this.$refs.wrapper.scrollTop;
    let pullRefreshScrollTop = this.$refs.pullRefresh.scrollTop;
    let listScrollTop = this.$refs.list.scrollTop;
    let itemScrollTop = this.$refs.item.scrollTop;

    console.log('wrapperScrollTop', wrapperScrollTop);
    console.log('pullRefreshScrollTop', pullRefreshScrollTop);
    console.log('listScrollTop', listScrollTop);
    console.log('itemScrollTop', itemScrollTop);
    
    //存儲 scrollTop 的值
    this.scroll = wrapperScrollTop;
   
    this.$router.push({name: "detail", params: {data: item}})
},

三、返回時取出并設(shè)置 scrollTop

上面已經(jīng)介紹過了,使用 keep-alive 之后,每次返回頁面會調(diào)用 activated 生命周期方法,所以在這個方法里設(shè)置之前記住的 scrollTop,達到記住滾動位置的效果。

代碼很簡單,只有一句話:

activated() {
    this.$refs.wrapper.scrollTop = this.scroll
}

完整的代碼如下:

<template>
  <div class="wrapper" ref="wrapper">
    <div class="title">我是標題</div>
    <van-pull-refresh v-model="isRefresh" @refresh="onRefresh" ref="pullRefresh">
      <van-list
          ref="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)" ref="item">
          <div class="item">{{item}}</div>
        </div>
      </van-list>
    </van-pull-refresh>
  </div>
</template>

<script>

  export default {


    components: {},
    created() {

    },
    mounted() {
      for (let i = 0; i < 15; i++) {
        this.list.push(i)
      }
    },
    data() {
      return {
        list: [], //列表數(shù)據(jù)
        loadingMore: false,  //加載更多是否顯示加載中
        finished: false, //加載是否已經(jīng)沒有更多數(shù)據(jù)
        isRefresh: false, //是否下拉刷新
        scroll: 0,
      }

    },

    activated() {
      this.$refs.wrapper.scrollTop = this.scroll
    },

    deactivated() {

    },
    methods: {

      clickItem(item) {
        let wrapperScrollTop = this.$refs.wrapper.scrollTop;
        let pullRefreshScrollTop = this.$refs.pullRefresh.scrollTop;
        let listScrollTop = this.$refs.list.scrollTop;
        let itemScrollTop = this.$refs.item.scrollTop;

        console.log('wrapperScrollTop', wrapperScrollTop);
        console.log('pullRefreshScrollTop', pullRefreshScrollTop);
        console.log('listScrollTop', listScrollTop);
        console.log('itemScrollTop', itemScrollTop);

        this.scroll = wrapperScrollTop;
        this.$router.push({name: "detail", params: {data: item}})
      },

      onRefresh() {
        this.list = [];
        this.finished = false;
        setTimeout(() => {
          for (let i = 0; i < 15; i++) {
            this.list.push(i)
          }
          this.isRefresh = false
        }, 2000)
      },

      //加載更多
      onLoadMore() {
        console.log('load more')
        let newList = [];
        for (let i = this.list.length; i < this.list.length + 15; i++) {
          newList.push(i)
        }
        this.list = this.list.concat(newList)
        this.loadingMore = false;
        if (this.list.length > 50) {
          this.finished = true
        }
      },

    }
  }
</script>

<style scoped lang="scss">
  @import "../../public/css/index";

  .wrapper {
    width: 100%;
    height: calc(100vh - 100px);
    overflow-x: hidden;
    box-sizing: border-box;
    margin-bottom: px2rem(50);
    .title{
      font-size: px2rem(20);
      padding: px2rem(10);
    }
    .list {
      width: 100%;
      flex: 1;
      display: flex;
      flex-direction: column;
      box-sizing: border-box;
      .item-wrapper {
        display: flex;
        flex-direction: column;
        font-size: px2rem(16);
        margin: px2rem(8);
        padding: px2rem(8);
        background-color: white;
        .item {
          font-size: px2rem(16);
          padding: px2rem(10);
        }
      }
    }
  }
</style>

好了,以上就是 Vue 返回記住滾動條位置的詳解。


全棧全平臺開源項目 CodeRiver

CodeRiver 是一個免費的項目協(xié)作平臺,愿景是打通 IT 產(chǎn)業(yè)上下游,無論你是產(chǎn)品經(jīng)理、設(shè)計師、程序員或是測試,還是其他行業(yè)人員,只要有好的創(chuàng)意、想法,都可以來 CodeRiver 免費發(fā)布項目,召集志同道合的隊友一起將夢想變?yōu)楝F(xiàn)實!

CodeRiver 本身還是一個大型開源項目,致力于打造全棧全平臺企業(yè)級精品開源項目。涵蓋了 React、Vue、Angular、小程序、ReactNative、Android、Flutter、Java、Node 等幾乎所有主流技術(shù)棧,主打代碼質(zhì)量。

目前已經(jīng)有近 100 名優(yōu)秀開發(fā)者參與,github 上的 star 數(shù)量將近 1000 個。每個技術(shù)棧都有多位經(jīng)驗豐富的大佬坐鎮(zhèn),更有兩位架構(gòu)師指導(dǎo)項目架構(gòu)。無論你想學什么語言處于什么技術(shù)水平,相信都能在這里學有所獲。

通過 高質(zhì)量源碼 + 博客 + 視頻,幫助每一位開發(fā)者快速成長。

項目地址:https://github.com/cachecats/coderiver


您的鼓勵是我們前行最大的動力,歡迎點贊,歡迎送小星星? ~

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • vue去哪網(wǎng)跟學筆記 記錄學習點滴 1. 初始化項目 1.1 手機顯示配適 minimum-scale=1.0,m...
    noobakong閱讀 2,420評論 0 16
  • vue概述 在官方文檔中,有一句話對Vue的定位說的很明確:Vue.js 的核心是一個允許采用簡潔的模板語法來聲明...
    li4065閱讀 7,598評論 0 25
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對于 Vue 1.0 印象不深的內(nèi)容。關(guān)于...
    云之外閱讀 5,173評論 0 29
  • VUE Vue :數(shù)據(jù)驅(qū)動的M V Vm框架 m :model(后臺提供數(shù)據(jù)),v :view(頁面),vM(模板...
    wudongyu閱讀 5,523評論 0 11
  • 今天電話母親,父親做了一次化療,身體受不了,腳無力,走路都走不太穩(wěn)。 幾個月的時間,病痛折磨人不已,電話中父親說話...
    命運之神閱讀 192評論 0 0

友情鏈接更多精彩內(nèi)容