乐趣区

关于javascript:小程序自定义下拉刷新组件为应对头部有非滚动区的情况

成果动图:

小程序是有其下拉刷新 api 的,然而头部或者尾部有非滚动区域的状况下,是应答不了的,相干问题在微信凋谢社区曾经是陈词滥调了,比方:

onPullDownRefresh 下拉刷新在安卓手机上会带动头部 fixed 元素一起下拉的问题

又比方:

小程序下拉刷新 onPullDownRefresh 问题,fixed 定位下移!

像这种问题有很多,不一一列举了。

这个问题从 2018 年开始就一直有开发者像微信凋谢社区反馈,始终到当初,都没有解决,没方法,本公司我的项目中有头部是固定非滚动的列表页面,只得本人自定义一个。

列表页的上拉加载,参考了这篇文章中的办法,链接放在这里,大家能够移步去看一下:

浅谈微信小程序中的下拉刷新和上拉加载

回到主题,下拉刷新,我的思路是在这篇文章里找到的:

小程序开发踩坑记录(五)——模仿实现底部 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 插槽替换成列表循环元素尝试。

退出移动版