淺談微信小程序中的下拉刷新和上拉加載

下拉刷新和上拉加載是業務上一個很常見的需求,在微信小程序裏,提供了下拉刷新的方法 onPullDownRefresh 。而實現上拉加載相對來講就比較不方便了。git

下拉刷新

雖然微信的官方文檔有不少坑,但下拉刷新介紹的仍是很全面的。在這裏稍稍帶過。github

  • 首先在全局 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>複製代碼

鉤子函數bash

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: 可滾動視圖區域。
它的具體用法不贅述,看官方文檔就好了。這裏提解決上述問題的方法便可。app

  • 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>複製代碼

以上就是最終的模板,你可能在想爲何這麼複雜。雖然複雜,但每一個屬性都是有用的,固然這其中有幾個坑在等着咱們。
首先節點分爲滾動容器和子容器。iphone

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
性能 流暢 節點過多會明顯卡頓
滾動函數 onPageScroll bindscroll
回到頂部 wepy.pageScrollTo(object) 默認有動畫效果,且沒法取消 設置節點屬性 scroll-top
坑點 暫時沒有發現 1, 與 enablePullDownRefreshReachBottom不能共存 2,不能綁定touchmove事件 3,不能觸發雙擊bar欄回到頂部的「彩蛋」

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

並無。

真機測試

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

總結

  1. 在微信小程序裏操做節點是昂貴的,比在瀏覽器裏操做還昂貴(這是經過比較上拉加載功能在3g端和微信小程序的流暢度得來的),在 1.4.0 版本發佈以後,雖然給出了不少操做節點的方法,好比獲得一個節點的寬高、或者經過 id 選擇器獲得一個節點。請儘可能減小這些方法的調用頻率( 函數節流 )或 緩存結果
  2. 動手以前先動腦!!!否則會走不少彎路...
相關文章
相關標籤/搜索