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

solocoder發表於2019-03-04

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

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

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

一共分三步:

  • 給 router-view 新增 keep-alive
  • 獲取並儲存當前 scrollTop
  • 返回時取出並設定 scrollTop

100 多位經驗豐富的開發者參與,在 Github 上獲得了 1000+star 的全棧全平臺開源專案想了解或參與嗎?
專案地址:https://github.com/cachecats/coderiver

一、給 router-view 新增 keep-alive

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

<keep-alive> 包裹動態元件時,會快取不活動的元件例項,而不是銷燬它們。所以在由詳情頁返回列表頁時,不讓列表頁重新整理。

當元件在 <keep-alive> 內被切換,它的 activateddeactivated 這兩個生命週期鉤子函式將會被對應執行。

設定 scrollTop 時是在 activated 方法裡,有些文章說獲取 scrollTopdeactivated 方法裡,但經過測試,在 deactivated 方法裡並不能準確的獲取 scrollTop 值,每次都是 0。具體原因暫時不深究,先解決問題。所以把獲取 scrollTop 值放在 item 的點選事件函式裡執行。

二、獲取並儲存當前 scrollTop

頁面佈局如下:

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

整個頁面是一個 <rounter-view> ,下面又分了兩個 tab,我們列表頁是一個元件,位於 title 和 導航欄之間的區域。

佈局程式碼:

<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 的點選事件裡觸發。得到的日誌如下:

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

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

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

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

因為使用了 keep-alive,頁面被快取起來了,所以 data 裡的資料不會丟失,可以在 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}})
},
複製程式碼

三、返回時取出並設定 scrollTop

上面已經介紹過了,使用 keep-alive 之後,每次返回頁面會呼叫 activated 生命週期方法,所以在這個方法裡設定之前記住的 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: [], //列表資料
        loadingMore: false,  //載入更多是否顯示載入中
        finished: false, //載入是否已經沒有更多資料
        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 是一個免費的專案協作平臺,願景是打通 IT 產業上下游,無論你是產品經理、設計師、程式設計師或是測試,還是其他行業人員,只要有好的創意、想法,都可以來 CodeRiver 免費釋出專案,召集志同道合的隊友一起將夢想變為現實!

CodeRiver 本身還是一個大型開源專案,致力於打造全棧全平臺企業級精品開源專案。涵蓋了 React、Vue、Angular、小程式、ReactNative、Android、Flutter、Java、Node 等幾乎所有主流技術棧,主打程式碼質量。

目前已經有近 100 名優秀開發者參與,github 上的 star 數量將近 1000 個。每個技術棧都有多位經驗豐富的大佬坐鎮,更有兩位架構師指導專案架構。無論你想學什麼語言處於什麼技術水平,相信都能在這裡學有所獲。

通過 高質量原始碼 + 部落格 + 視訊,幫助每一位開發者快速成長。

專案地址:https://github.com/cachecats/coderiver


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

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

相關文章