淺談微信小程式中的下拉重新整理和上拉載入

丁香園F2E發表於2018-02-05

下拉重新整理和上拉載入是業務上一個很常見的需求,在微信小程式裡,提供了下拉重新整理的方法 onPullDownRefresh 。而實現上拉載入相對來說就比較不方便了。

下拉重新整理

雖然微信的官方文件有很多坑,但下拉重新整理介紹的還是很全面的。在這裡稍稍帶過。

  • 首先在全域性 config 中的 window 配置 enablePullDownRefresh .
  • Page 中定義 onPullDownRefresh 鉤子函式。到達下拉重新整理條件後,該鉤子函式執行,發起請求方法。
  • 請求返回後,呼叫 wx.stopPullDownRefresh 停止下拉重新整理。

config

config = {
    pages: [
      'pages/index'
    ],
    window: {
      backgroundTextStyle: 'light',
      navigationBarBackgroundColor: '#ccc',
      navigationBarTitleText: 'WeChat',
      navigationBarTextStyle: '#000',
      enablePullDownRefresh: true
    }
  }複製程式碼

page

onPullDownRefresh() {
  wepy.showNavigationBarLoading() 
  setTimeout(()=>{
    this.getData = '資料拿到了'
    wepy.stopPullDownRefresh()
    wepy.hideNavigationBarLoading()
    this.$apply()
  },3000)
}複製程式碼

效果如下:
image
你會發現下拉的過程有些僵硬。這實際上是沒有新增背景色的原因,加上背景色後再試試。
image
現在感覺好多了吧。下拉重新整理有現成的配置和方法,很容易實現,可上拉載入就不同了。

上拉載入

首先看一下要實現的效果,這是3g端的上拉載入。小程式要實現同樣的效果。
image
首先功能有

  • 點選回到頂部 這個很好實現,有對應的回到頂部函式
  • 滑動螢幕記錄當前頁數 這個也很好實現,主要是監聽滾動事件,判斷對應滾動條高度,去計算其與子容器的高度即可。
  • 上拉載入動畫

這裡有兩個實現的方案。一個是 page 自帶的下拉觸底鉤子事件 onReachBottom 能做的只是下拉到底部的時候通知你觸底了,一個是 scroll-view 標籤自帶事件。現在用兩個方法分別實現一下上拉載入。

上拉觸底事件 onReachBottom

模板

<template>
  <view class="loading"></view>
  <view class="container"  
        @touchmove="moveFn" 
        @touchstart="startFn" 
        @touchend="endFn"
        style="transform:translate3d(0,{{childTop}}px,0)">
    <repeat for="{{list}}" 
            key="index" 
            index="index" 
            item="item">
        <view>{{ item }}<text>{{index}}</text></view>
    </repeat>
    </view>
</template>複製程式碼

鉤子函式

data = {
  getData: '',
  top: 0,
  lastTop: 0,
  canDrag: false,
  list: []
}
onReachBottom() {
 this.canDrag = true
}
methods = {
  moveFn(ev) {
    let nowY = ev.changedTouches[0].clientY
    nowY = nowY-this.lastTop
    if(nowY > 0 )
      this.canDrag = false
    if( nowY<=0 && this.canDrag ) {
      this.top = nowY
    }
    if( -this.top>= this.maxTop  )
      this.top = -this.maxTop
  },
  startFn(ev) {
    this.lastTop = ev.changedTouches[0].clientY 
  },
  endFn() {
    if(this.top <= -this.maxTop) {
      this.text = "去請求資料了"
      setTimeout(()=>{
        this.text = "請求回來了"
        this.canDrag = false
        this.list.push(...["資料","資料","資料"])
        this.$apply()
        this.top = 0;
        return
      },1000)
    }
  },
  gotoTop() {
    wepy.pageScrollTo({
      scrollTop: 0
    })
  }
}複製程式碼

完成後看一下效果:
image

滾動容器實現上拉載入

scroll-view: 可滾動檢視區域。
它的具體用法不贅述,看官方文件就行了。這裡提解決上述問題的方法即可。

  • bindscrolltolower 類比原生全域性鉤子 onReachBottom
    模板
<scroll-view    scroll-y 
                id="content"   
                @scroll="scroll"  
                @scrolltolower="lower" 
                scroll-top="{{gotoTopNum}}" 
                lower-threshold="100" 
                style="transform:translate3d(0,{{childTop}}px,0)">
    <view  class="sty-search" 
            @touchmove="moveContent" 
            @touchstart="startContent" 
            @touchend="endContent">...</view>
</scroll-view>複製程式碼

以上就是最終的模板,你可能在想為什麼這麼複雜。雖然複雜,但每個屬性都是有用的,當然這其中有幾個坑在等著我們。
首先節點分為滾動容器和子容器。

Q:為什麼滾動容器裡巢狀一個子容器,並且將拖動的三個方法繫結在它上面。
A:這是第一個坑,因為 scroll-view 容器不能繫結 touchmove 事件,那如果繫結了會怎麼樣呢?不會怎麼樣,事件鉤子不會呼叫。(這個坑在官方文件查不出來,當時繫結了不呼叫,在社群找到了解決方法,就是將touchmove事件繫結到子容器)
再來看程式碼

methods = {
    async lower() {
      this.canDrag = true
    },
    scroll (ev) {
      this.scrollTop = ev.detail.scrollTop
      if (ev.detail.deltaY > 0) {
        this.canDrag = false
      }
      let nowSet = this.documentHeight+this.scrollTop-this.contentHeader
      let num = Math.ceil(nowSet/this.listHeight) - 1
      num = Math.floor(num / this.pageBean.pageSize) + 1
      num = (num > this.pageBean.pageNo) ? this.pageBean.pageNo : num 
      if(num != this.page) {
        this.page = num
        this.$apply()
      }
    },
    startContent(ev) {
      this.lastTop = ev.changedTouches[0].clientY
      if(!this.documentHeight){
        this.documentHeight = wx.getSystemInfoSync().windowHeight
      }
      /* 這句是解決回到頂部的bug */
      if (this.gotoTopNum || this.gotoTopNum==0) { this.gotoTopNum = undefined }
    },
    moveContent (ev) {
      let {
        pageNo,
        pageSize,
        totalCount
      } = this.pageBean
      let nowY = ev.changedTouches[0].clientY
      nowY = nowY-this.lastTop
      if (this.canDrag && nowY) {
          this.state = 1;
          if (nowY <= -this.maxMove) {
            nowY = -this.maxMove
          }
          if (nowY <= 0) {
            this.childTop = nowY
          } 
      }
    },
    async endContent(ev) {
      let {
        pageNo,
        pageSize,
        totalCount
      } = this.pageBean
    
      if (this.childTop === -this.maxMove) {
        
        /* 狀態 */
        if (pageNo >= this.maxPage || pageNo * pageSize >= totalCount) {
            this.state = 0
        } else {
          this.pageBean.pageNo++ 
          await this.fillData()
          this.childTop = 0
          this.canDrag = false
          this.$apply()
        }
      }
      /* 如果沒超過重新整理高度則重置 */
      this.childTop = 0
    },
    gotoTop() {
      this.gotoTopNum = 0
    },
}
複製程式碼

Q: 為什麼要在 touchStart 的時候 將 gotoTopNum 置為 undefined?
A: 因為這個頁面有一個回到頂部的功能,當回到頂部時,gotoTopNum 置為0,再次下翻時,雖然實際的 scrollTop 改變了,但是 gotoTopNum 還為0,再次點選回到頂部時,因為資料未改變,檢視層就不會去更新。所以在 touchStart 的時候給 gotoTopNum 一個無效的值,再次點選回到頂部時,檢視層也就更新了。

image

原生滾動 OR scroll-view

對比原生滾動scroll-view
效能流暢節點過多會明顯示卡頓
滾動函式onPageScrollbindscroll
回到頂部wepy.pageScrollTo(object) 預設有動畫效果,且無法取消設定節點屬性 scroll-top
坑點暫時沒有發現1, 與 enablePullDownRefreshReachBottom不能共存 2,不能繫結touchmove事件 3,不能觸發雙擊bar欄回到頂部的“彩蛋”

END...了嗎......

並沒有。

真機測試

實現的上拉載入在模擬器上跑的很流暢,不存在問題。可是。
如果是蘋果機的話(暫時測試iphone5 和 iPhone7),存在這樣一個問題,上拉或下拉回彈效果,這個效果會影響上拉的距離。
這個問題想了很久,目前不能優雅的解決。
所以就找產品經理修改了需求,去掉了上拉動畫效果 所以最終的效果就變成:
image

總結

  1. 在微信小程式裡操作節點是昂貴的,比在瀏覽器裡操作還昂貴(這是通過比較上拉載入功能在3g端和微信小程式的流暢度得來的),在 1.4.0 版本釋出之後,雖然給出了很多操作節點的方法,比如得到一個節點的寬高、或者通過 id 選擇器得到一個節點。請儘量減少這些方法的呼叫頻率( 函式節流 )或 快取結果
  2. 動手之前先動腦!!!不然會走很多彎路...


相關文章