效果動圖:ios
小程序是有其下拉刷新api的,然而頭部或者尾部有非滾動區域的狀況下,是應對不了的,相關問題在微信開放社區已是老生常談了,好比:spring
onPullDownRefresh 下拉刷新在安卓手機上會帶動頭部fixed元素一塊兒下拉的問題小程序
又好比:微信小程序
小程序下拉刷新onPullDownRefresh 問題,fixed定位下移!api
像這種問題有不少,不一一列舉了。微信
這個問題從2018年開始就不斷有開發者像微信開放社區反饋,一直到如今,都沒有解決,沒辦法,本公司項目中有頭部是固定非滾動的列表頁面,只得本身自定義一個。動畫
列表頁的上拉加載,參考了這篇文章中的方法,連接放在這裏,你們能夠移步去看一下:this
回到主題,下拉刷新,個人思路是在這篇文章裏找到的:.net
小程序開發踩坑記錄(五)——模擬實現底部tabbar和下拉刷新功能(解決安卓端打開下拉刷新功能後fixed元素失效問題)
我一上來打算用該文章中的方法,來自制下拉刷新,但是我是在自定義組件中使用,不是在page中定義,在自定義組件中scroll-view的onscroll事件怎麼也響應不了,也就沒法得到當前頁面的scrollTop。
後來我受這個啓發,既然能夠在tocuh事件中改變scroll-view的scrollTop,來模擬出一個下拉刷新區域的scrollTop隨手指滑動而變化,進而實現其下拉顯示和回彈,那我也能夠使用絕對定位和相對定位,依靠改變容器的top值來實現這個功能,也就是能夠將滾動容器的top初始值設置爲負的刷新區域的高度,在tocuhstart和tocuhmove事件中獲取手指在屏幕上的滑動距離,讓容器的top和滑動距離呈正相關,當top值達到本身設置的閾值的時候中止變化,隨後在tocuhend事件中發起刷新請求。
大致思路如上圖所示
下面貼一下基本代碼:
<view class="scroll" scroll-y style="min-height: {{pageHeight + 'px'}}; top: {{marginTop + 'rpx'}}" enable-back-to-top="{{true}}" bindtouchmove="movePull" bindtouchstart="startPull" bindtouchend="endPull" bindscroll="onScroll" > <view class="scroll__content {{springbacking ? 'return__content' : ''}}" style="min-height: {{contentHeight + 'px'}};top: {{scrollTop + 'px'}}"> <view class="refresh__wrap"> <view class="refresh__center"> <view class="refresh__icon {{refreshText === '鬆開刷新' ? 'rotate' : ''}}" > <mp-icon icon="sending" color="#999" size="{{35}}"></mp-icon> </view> <view class="tips">{{refreshText}}</view> <view class="times">最後更新:{{refreshTime}}</view> </view> </view> <slot /> <view class="footer__line" wx:if="{{recycleList.length}}"> <view class="scroll__loading" wx:if="{{bottomLoadingShow}}"></view> <text class="text {{bottomLoadingShow ? 'notline' : ''}}">{{lineText}}</text> </view> </view> <no-data show="{{notDataShow}}" bind:refresh="refresh" /> </view>
重點說下,這個組件結構爲外層.scroll元素,最小高度設置爲pageHeight(計算方法後面會貼),position爲relative,其top值設置爲父級頁面傳入的marginTop值,就是父級頁面頭部非滾動元素的rpx高度,若是沒有非滾動元素,則默認爲0,這樣的話,外層的位置就能夠依賴marginTop值來肯定了,滾動的時候不會把非滾動區域也算進去;
內層列表滾動區域.scroll__content元素,position爲absolute最小高度contentHeight,top值scrollTop,初始-80px(刷新文案元素高度),這樣就把.refresh__wrap刷新文案元素隱藏起來了。此外,設置一個默認插槽,就是列表循環內容。
pageHeight和contentHeight計算方法,用到wx.getSystemInfozhege api,比較簡單:
getPageHeight() { const self = this; wx.getSystemInfo({ success: (res) => { self.setData({ pageHeight: res.windowHeight - (self.data.marginTop / 750 * res.windowWidth), contentHeight: (res.windowHeight - (self.data.marginTop / 750 * res.windowWidth)) + REFRESHHEIGHT + 2, }) } }) }
以上,marginTop是父級組件傳進來的頭部非滾動區域的rpx高度值(頭部非滾動區域position設置爲fixed),默認爲0,REFRESHHEIGHT是80,就是刷新文案區域的高度px值,最後加2是保證在第一頁鋪不滿整屏的狀況下,能讓onReachBottom上拉事件生效,上拉加載本文很少說了,能夠直接去看上面貼出的連接:「淺談微信小程序中的下拉刷新和上拉加載」(注意,我這裏是自定義組件,onReachBottom事件一樣要在父頁面中寫onReachBottom生命週期,來調用自定義組件中的onReachBottom方法)。
tocuhstart方法,就是獲取手指開始接觸屏幕的clentY值:
startPull(ev) { this.lastTop = ev.changedTouches[0].clientY; },
tocuhmove方法:
// MAX_MOVE_TOP 爲 120 容許最大滑動距離 // MAX_SCROLL_TOP 爲 20 容許.scroll__content的最大top值 movePull(ev) { this.nowY = ev.changedTouches[0].clientY;// 手指當前觸摸位置的clentY值 this.nowY = this.nowY - this.lastTop;// 滑動距離 const query = wx.createSelectorQuery(); query.select('.scroll').boundingClientRect(); query.selectViewport().scrollOffset(); query.exec((rect) => { // 必須是滾動高度爲0即在頂部的時候觸發 if (rect[1].scrollTop <= 0 && this.nowY > 0 && this.nowY <= MAX_MOVE_TOP && this.data.recycleList.length) { this.setData({// 知足以上條件的,則使.scroll__content元素的top值等於-80px 加上滑動距離 nowY scrollTop: -REFRESHHEIGHT + this.nowY, }) if(this.nowY >= 100) { this.setData({ refreshText: '鬆開刷新', }) } } }) if(this.nowY > MAX_MOVE_TOP && this.data.scrollTop < MAX_SCROLL_TOP) { this.setData({// 此處判斷是爲了解決手指滑動過快,tocuhmove獲得的clentY值呈非線性變化,致使滑動距離可能上一次仍是100之內,下一次直接就到300開外,沒法知足上面的top變化條件,就卡住了。因此此時手動將.scroll__content的top值設置爲20。 refreshText: '鬆開刷新', scrollTop: MAX_SCROLL_TOP, }) } ... // 上拉加載邏輯 ... }
tocuhend事件:
endPull() {// 結束滑動的時候當.scroll__content的top值大於等於20,則能夠執行刷新方法。 if(this.data.scrollTop >= MAX_SCROLL_TOP) { wx.showNavigationBarLoading(); this.refresh(); } ... // 上拉加載邏輯 ... if (this.data.scrollTop > -REFRESHHEIGHT) { this.setData({ // 這個springbacking爲true的時候,.scroll__content元素的transition就是0.4s,回彈時候的動畫效果。 springbacking: true, scrollTop: -REFRESHHEIGHT }) } },
好了,下拉刷新的邏輯就寫完了,該下拉刷新在安卓和ios上的效果差異不是很大,因爲業務須要,我是把他作成了一個組件,一些刷新方法和父頁面的請求事件成功交互,失敗交互,不在本文探討範圍以內,因此就略去了,想試試的朋友能夠直接在頁面中將slot插槽替換成列表循環元素嘗試。