注意:這里說的是返回頁面滾動位置狀態保持,不是簡單的keep-alive實現的頁面緩存。
應用場景:
A頁面為首頁,B頁面也為列表頁面,C頁面為B頁面的某個列表項詳情頁面:
A->B->C:A頁面進入B頁面,滾動到某個列表項 list-item-x ,點擊列表項進入頁面C。
C->B->A:對於返回的操作,C頁面返回B頁面,B頁面應該保持在 list-item-x ,B頁面返回A頁面(如果A頁面有滾動也應該有滾動保持)。
需求分析:
1.使用keep-alive?
keep-alive只能使當前訪問頁面緩存,再下次再次進入的時候直接訪問之前緩存的頁面,不會再有數據請求更新。這樣就會出現這種情況,即加入A、B、C三個頁面都被緩存,那么在進行A->B->C訪問之后,無論是返回還是再訪問,那么三個頁面都是直接訪問緩存頁面了,這個就比較尷尬了。
雖然這個問題可以通過beforeEach對指定路由跳轉進行動態配置,來確定頁面是否需要緩存。也可以結合Keep-alive的 activated deactivated 生命周期的用法來控制當前訪問緩存內容還是重新請求,但無論哪種方法,都是比較麻煩的。
而且,keep-alive組件只能緩存頁面數據渲染,並不能保存頁面滾動,也就是說頁面返回仍會返回到頂部展示。如果要做到滾動保持,仍需特殊處理。經驗證,hash模式下使用better-scroll插件來保持滾動狀態會比較好些。
鑒於以上 keep-alive 的表現,最終還是決定采用 popstate beforeEach 及better-scroll插件來組合實現頁面滾動狀態保持。
2. 使用better-scroll實現,需要考慮這些問題:
1)返回的監聽:vue-router是無法直接判斷頁面時返回還是訪問的,所以如果想做到只在頁面返回時對頁面滾動做處理,那么就需要用到 popstate 來監聽頁面返回了;
2)滾動距離的記錄:可以使用Better-scroll來記錄元素滾動的距離,然后再下次頁面加載的時候,如果是頁面返回且有頁面滾動距離,那么就要做自動滾動處理了;
3)頁面滾動的重置:對於頁面中有tab切換,以及返回上級頁面,都是需要重置當前頁面的滾動及返回訪問這些標記的,以確保tab切換之后或者返回上一頁再次進入當前頁面不會保留之前的滾動。這個需要把握好頁面返回、滾動距離等相關標記變量賦值、重置的時機;
4)多頁面引用:可以考慮封裝一個mixin來給所有需要滾動保持的頁面使用。
解決方案:
1)使用 popstate 監聽返回頁面,結合 beforeEach 對返回頁面進行標記,如此就能知道頁面訪問是不是返回了。
2)使用better-scroll記錄滾動元素的滾動距離,然后在 beforeRouteLeave 中保存到路由元中。
3)在 created 生命周期方法中判斷是否是返回訪問,如果是則保存路由元中保存的滾動距離到當前頁面變量中。
4)在better-scroll中實現自動滾動,並重置頁面路由的相關滾動返回標記變量。
5)使用混入(mixin)對滾動相關業務邏輯進行封裝,這樣可以給所有需要滾動的頁面進行使用(需要考慮所有情況的兼容性)。
大致流程圖如下:
如上,關鍵的幾個邏輯分別標了序號,並在左下角做了簡單說明,可對照理解。
關鍵代碼:
scrollMixin.js
1 /** 2 * 作者:xyytcch@foxmail.com 3 * 時間:2020-07-06 4 * 描述:頁面狀態保持 5 * isReturn:非常重要!!!保持頁面狀態所有操作的必要前提(返回路由元獲取,beforeEach中重置) 6 * scrollTop:頁面滾動距離(頁面返回路由元獲取,頁面滾動獲取||頁面初始化中重置) 7 * flag_index:tab默認選中索引(頁面返回路由元獲取,非返回不獲取) 8 * isReturnScroll:返回訪問需要保持滾動位置(自動滾動后會重置路由元中的相關變量) 9 */ 10 import Bscroll from 'better-scroll' 11 export default function() { 12 return { 13 data() { 14 return { 15 flag_index: 0, //tab當前索引 16 scroll: '', //better-scroll示例對象 17 scrollTop: 0, //頁面(指定元素)距離頂部滾動距離 18 isReturnScroll:''//標記頁面返回訪問需要保持滾動狀態 19 } 20 }, 21 created() { 22 //頁面加載,獲取頁面返回、滾動相關標記 23 this.autoScroll(0) 24 }, 25 beforeRouteLeave(to, from, next) { 26 //組件失活,保存頁面滾動距離、tab索引到路由配置中 27 this.autoScroll(2) 28 next() 29 }, 30 methods: { 31 autoScroll(type) { 32 let returnData = ''; 33 //記錄頁面指定元素滾動位置 34 let routeName = this.$route.path; 35 this.$router.options.routes.map((item, index) => { 36 if(item.path == routeName) { 37 //獲取頁面返回標記 38 let isReturn = item.meta.isReturn || false 39 if(type == 0 && isReturn) { 40 //1.頁面創建,返回路由配置對象給到頁面 41 //tab索引、滾動距離都必須要在有返回標記的情況下才取用) 42 this.flag_index = item.meta.flag_index || 0 43 this.scrollTop = item.meta.scrollTop || 0 44 this.isReturnScroll=true 45 } else if(type == 2) { 46 //2.組件失活,保存頁面滾動距離、tab索引到路由配置中 47 if(item.meta) { 48 item.meta.scrollTop = this.scrollTop 49 } else { 50 item['meta'] = { 51 'scrollTop': this.scrollTop 52 } 53 } 54 if(this.flag_index > 0) item.meta['flag_index'] = this.flag_index 55 } else { 56 //非返回訪問,重置路由配置 57 item.meta = {} 58 } 59 } 60 }) 61 if(returnData) return returnData 62 }, 63 /** 64 * 列表滾動事件處理 65 * @param {Object} list 66 */ 67 doScroll(pullingDownRefresh) { 68 var self = this 69 if(!self.scroll) { 70 self.scroll = new Bscroll(self.$refs.wrapper, { 71 click: true, 72 startY: self.isReturnScroll?self.scrollTop:0,//自動滾動處理 73 // 下拉刷新 74 pullDownRefresh: { 75 // 下拉距離超過30px觸發pullingDown事件 76 threshold: 30, // 回彈停留在距離頂部20px的位置 77 stop: 0, 78 } 79 }) 80 81 //下拉刷新 82 self.scroll.on('pullingDown', () => { 83 if(pullingDownRefresh) pullingDownRefresh() 84 setTimeout(() => { 85 // 事情做完,需要調用此方法告訴 better-scroll 數據已加載,否則下拉事件只會執行一次 86 self.scroll.finishPullDown() 87 }, 1000) 88 }) 89 90 //保存滾動位置 91 self.scroll.on('scrollEnd', (poy) => { 92 self.scrollTop = poy && poy.y 93 }) 94 } else { 95 self.scroll.refresh() 96 self.scroll.scrollTo(0, 0) //必須添加這個,不然兩個tab切換的時候滾動位置會相互影響 97 } 98 } 99 } 100 } 101 }
如上,doScroll函數主要是處理better-scroll的初始化、自動滾動、下拉刷新、上拉加載、滾動監聽等。autoScroll函數則主要封裝頁面返回自動滾動相關標記變量的獲取、重置,以及相關業務邏輯處理(包含的情況有點多,需要好好梳理)。
router.js
1 //監聽返回處理 2 let returnUrl = ''//返回頁面的路徑 3 window.addEventListener("popstate", () => { 4 //當前頁面觸發,location則是要返回的目標頁面 5 returnUrl = location.hash 6 }, false); 7 8 router.beforeEach((to, from, next) => { 9 document.title = "9分享兌" 10 router.to = to 11 router.from = from 12 13 //標記當前頁面訪問是歷史記錄返回 14 router.options.routes.map((item, index) => { 15 if(!item.meta) item['meta'] = {} 16 //判斷當前頁面是否是返回訪問 17 if(returnUrl && returnUrl.indexOf(item.path) > -1) { 18 item.meta.isReturn = true 19 } else { 20 item.meta.isReturn = false 21 } 22 }) 23 console.log(router.options.routes[4]) 24 next() 25 })
如上,popstate監聽頁面返回,標記返回頁面的路徑(點擊返回即觸發,可以真實的記錄每次要返回頁面的路徑)。在beforeEach中根據返回監聽獲取的頁面相關路徑來對返回頁面的路由進行路由元配置,添加頁面返回標記(必須要用路由元,只有配置到相應頁面中,才能對指定頁面進行頁面狀態保持處理)。
頁面引用:
<template> <div class="container h100"> <!--頂部tab--> <tab class="tabView orderTab border_b" default-color="#999999" :animate="false" active-color="#f51406"> <tab-item class="f15" :selected="flag_index==index" @on-item-click="tabChange(index)" v-if="flag_list.length>0" v-for="(item,index) in flag_list" :key="index">{{item}}</tab-item> </tab> <!--頂部tab ebd--> <!--訂單列表--> <div id="taskList" v-if="list&&list.length>0" class="w100 f13" ref="wrapper"> <div class="content"> ...... </div> </div> <!--訂單列表 end--> ...... </div> </template> <script> ...... import Mixin from '@assets/js/scrollMinin'; let mixin = new Mixin() export default { name: "myOrder", mixins: [mixin], ...... methods: { ...... /** * 初始化頁面 */ initPage() { this.isEmpty = '' this.list = '' this.scroll = '' this.scrollTop=0 this.searchUserOrderList() }, /** * 查詢訂單列表 */ searchUserOrderList() { var self = this http.searchUserOrderList(self.orderListParams).then(data => { ...... self.$nextTick(() => { self.doScroll(self.initPage)//下拉刷新需要調用頁面初始化函數 }) ...... }) }, ...... } } </script>
如上,在頁面中調用,要處理好better-scroll容器的展示,如果需要下拉刷新,則須封裝一個頁面初始化函數傳到mixin中。
注意事項:
- 本例將better-scroll的相關使用以及頁面狀態保持相關業務邏輯封裝在一個文件中,采用混入的方式進行調用;
- 需要保持狀態相關的變量及函數要在mixin文件中進行聲明,混入頁面后可以直接調用;
- 除了滾動位置保持,示例代碼中還有tab選擇保持(當前頁面tab切換,需要注意tab切換時重置頁面滾動位置變量;
- 聲明了組合變量 isReturnScroll ,如有需要,可以在頁面渲染的時候進行一些干預(如滾動容器中有輪播和數據列表,為不同接口異步獲取,如做滾動保持,那么可能會出現比較明顯的閃動切換,那么就可以把輪播數據的獲取寫在列表數據獲取之后,雖然還是異步,但基於js的執行順序,可以有效的降低自動滾動切換時的閃動)
后記:
如果要做tab選擇狀態保持,需要控制好tab索引變量,畢竟是采用混入(mixin)方式引用,所有引用頁面勢必要依賴混入變量(tab選擇索引)。tab選擇索引的混入變量默認值為0,因此需要在各引用頁面中處理好tab索引的相關展示以及接口請求。