在开发小程序的时候通常会遇到上拉或者下拉刷新的功能需求,然而这个功能很多页面也都会用到,因此这里,把这个功能封装为组件,方便复用
我很直接,不多说,上代码
首先index.wxml
<scroll-view scroll-y="{{ scrollY }}" style="height:{{ height }}" class="prv-container" enable-flex show-scrollbar="{{ false }}" enhanced scroll-anchoring refresher-enabled="{{ refresherEnable }}" bindrefresherrefresh="_onRefresh" refresher-triggered="{{ refreshing }}" bindscrolltolower="_onLoadmore" bindrefresherpulling="_onPulling" refresher-default-style="none" bindrefresherrestore="_onClose" bindscrolltoupper="_onScrollTop" refresher-threshold="{{ pullDownHeight }}" scroll-into-view="{{ scrollToView }}" scroll-with-animation="{{ scrollWithAnimation }}" scroll-top='{{ topNum }}' bindscroll="onScroll" bindtouchend="handlerTouchend" lower-threshold="70">
  <slot slot="refresher" name="refresher" wx:if="{{ refresherType=='custom' }}"></slot>
  <view slot="refresher" class="prv-pulldown" style="height:{{ pullDownHeight }}px;line-height:{{ pullDownHeight }}px;" wx:else>
    <view wx:if="{{ refresherType=='default' }}">
      <block wx:if="{{ pullState==0||pullState==1 }}">
        <view class="prv-pull-icon" style='transform:rotate({{ pullState==0?0:180 }}deg);'></view>
        <text class="prv_text" wx:if="{{ pullState==0 }}"> {{ pullText }}</text>
        <text class="prv_text" wx:if="{{ pullState==1 }}" space="nbsp"> {{ releaseText }}</text>
      </block>
      <block wx:if="{{ pullState==2 }}">
        <view class="prv-loading"></view><text> {{ refreshText }}</text>
      </block>
    </view>
    <view wx:elif="{{ refresherType=='circle' }}" class="prv-loading prv-dot-loading">
    </view>
  </view>
  <slot></slot>
  <slot name="loader" wx:if="{{ loadType=='custom'&&!isEmpty&&showLoading }}"></slot>
  <view class="prv-loadmore" wx:elif="{{ !isEmpty&&showLoading }}">
    <block wx:if="{{ nomore }}">
      <text class="loadmore_text">{{ nomoreText }}</text>
    </block>
    <block wx:else>
      <view class="prv-loading"></view><text>{{ loadmoreText }}</text>
    </block>
  </view>
</scroll-view>
<!-- 置顶 -->
<view wx:if="{{ showTop }}" class="go-top" bindtap="goTop">置顶</view>
 
接着index.wxss
:host {
  position: relative;
  overflow: hidden;
  display: block;
}
.prv-container {
  display: flex;
  flex-direction: column;
  position: relative;
  width: 100%;
}
.prv-scroll-view {
  height: 100%;
  flex: 1;
}
.prv-pulldown {
  width: 100%;
  text-align: center;
  display: block;
  font-size: 26rpx;
}
.prv-pulldown .prv_text {
  color: #222;
}
.prv-loadmore {
  height: 120rpx;
  width: 100%;
  line-height: 120rpx;
  text-align: center;
  font-size: 26rpx;
}
.prv-loadmore .loadmore_text {
  color: #444;
}
@keyframes loading {
  0% {
    transform: rotate(0deg)
  }
  50% {
    transform: rotate(180deg)
  }
  100% {
    transform: rotate(360deg)
  }
}
.prv-pull-icon {
  width: 18px;
  height: 18px;
  display: inline-block;
  vertical-align: middle;
  transition: all 0.6s ease;
  background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAAJ1BMVEUAAABvb29wcHBzc3NwcHBwcHBwcHBwcHBwcHBwcHBvb29vb29wcHAefdnwAAAADHRSTlMAVdQqRTL07OPbTD3LST/yAAABVElEQVRo3u3OsU0EUQyE4UUnEkiuALITGcHqKiCjBlpAlEFIMbQACUJyUdwfOZiVvXZwkSd4z3Yw+pbJJI9dMgVTMAVTMAVTMAVTMAVTMAVTMAVTcO2CddnK7bq34PC4WfB62ltw/vtaNPefP+u+gsOTfSyaF7PTvoKz2e+zXO/ezSCkBQDM3qTgwQxCWgCAHDcKIIQFDrBvEdzYFoFbAMgJ3AJATuAUAHICpwCQE7gEgJzAJQDkBA4BICdwCAA5gT0A5AT2AJATWANATmBVQIHApoACgU0AFQKLACoEFgFUCMwCqBCYBVAhMAqgQmAUQIXAJIAKgUkAFQKDACoEBgFUCPwCqBD4BVAh8DUATuBzQIPA64AGgdcBDQKPAxoEHgc0CKQHgOAB0CP0ARAE0CYAaBP6AAgOaBMc0CcA6BMc0CcA6BMc0CQ4oEtwQJNwXCaTPP9ccLYeafVjOwAAAABJRU5ErkJggg==) no-repeat;
  background-size: 100%;
}
.prv-loading {
  width: 20px;
  height: 20px;
  display: inline-block;
  vertical-align: middle;
  margin-top: -2rpx;
  animation: weuiLoading 1s steps(12, end) infinite;
  background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
  background-size: 100%
}
@keyframes weuiLoading {
  0% {
    transform: rotate3d(0, 0, 1, 0deg)
  }
  100% {
    transform: rotate3d(0, 0, 1, 360deg)
  }
}
.prv-dot-loading,
.prv-dot-loading:before,
.prv-dot-loading:after {
  display: inline-block;
  vertical-align: middle;
  width: 6px;
  height: 6px;
  -webkit-border-radius: 50%;
  border-radius: 50%;
  background-color: rgba(0, 0, 0, 0.3);
  font-size: 0;
  animation: dot2 1s step-start infinite
}
.prv-dot-loading {
  position: relative
}
.prv-dot-loading:before {
  content: "";
  position: absolute;
  left: -12px;
  background-color: rgba(0, 0, 0, 0.1);
  animation: dot1 1s step-start infinite
}
.prv-dot-loading:after {
  content: "";
  position: absolute;
  right: -12px;
  background-color: rgba(0, 0, 0, 0.5);
  animation: dot3 1s step-start infinite
}
@keyframes dot1 {
  0%,
  100% {
    background-color: rgba(0, 0, 0, 0.1)
  }
  30% {
    background-color: rgba(0, 0, 0, 0.5)
  }
  60% {
    background-color: rgba(0, 0, 0, 0.3)
  }
}
@keyframes dot2 {
  0%,
  100% {
    background-color: rgba(0, 0, 0, 0.3)
  }
  30% {
    background-color: rgba(0, 0, 0, 0.1)
  }
  60% {
    background-color: rgba(0, 0, 0, 0.5)
  }
}
@keyframes dot3 {
  0%,
  100% {
    background-color: rgba(0, 0, 0, 0.5)
  }
  30% {
    background-color: rgba(0, 0, 0, 0.3)
  }
  60% {
    background-color: rgba(0, 0, 0, 0.1)
  }
}
.go-top{
  position: fixed;
  right: 21rpx;
  bottom: 170rpx;
  border-radius: 50%;
  font-size: 22rpx;
  color: #fff;
  background: rgba(0,0,0,0.5);
  width: 70rpx;
  height: 70rpx;
  text-align: center;
  line-height: 70rpx;
}
 
然后index.js
// 支持下拉刷新-上拉加载的组件
Component({
  options: {
    // 加入下面一段代码才能正常使用slot的name区分
    multipleSlots: true
  },
  properties: {
    height: {
      type: String,
      value: '84vh'
    },
    refresherEnable: {
      type: Boolean,
      value: true
    },
    refresherType: {
      type: String,
      value: 'default',
    },
    loadType: {
      type: String,
      value: 'default',
    },
    pullText: {
      type: String,
      value: '下拉刷新',
    },
    releaseText: {
      type: String,
      value: '松开立即刷新',
    },
    refreshText: {
      type: String,
      value: '正在刷新',
    },
    loadmoreText: {
      type: String,
      value: '加载中',
    },
    nomoreText: {
      type: String,
      value: '没有更多数据',
    },
    pullDownHeight: {
      type: Number,
      value: 60,
    },
    refreshing: {
      type: Boolean,
      value: false,
      observer: '_onRefreshFinished',
    },
    scrollY: {
      type: Boolean,
      value: true
    },
    nomore: {
      type: Boolean,
      value: false,
    },
    showLoading: {
      type: Boolean,
      value: true,
    },
    scrollToView: {
      type: String,
      value: ''
    },
    scrollWithAnimation: {
      type: Boolean,
      value: false,
    }
  },
  data: {
    showTop: false,
    pullState: 0, // 下拉状态
    lastScrollEnd: 0,
    scrollTop: 0,
    isLoadMore: false,
    moveY: -60,
    topNum: 0,
  },
  attached() {
    this.setData({
      endY: -this.properties.pullDownHeight
    })
  },
  methods: {
    // 被下拉
    _onPulling: function (e) {
      console.log('被下拉', e)
      let y = e.detail.dy
      if (y < this.properties.pullDownHeight) {
        if(this.data.pullState == 0) return
        this.setData({
          pullState: 0
        })
      } else {
        if(this.data.pullState == 1) return
        this.setData({
          pullState: 1
        })
      }
      // this.triggerEvent('onpulling', this.data.pullState)
    },
    // 滚动到顶部
    _onScrollTop: function (e) {
      console.log('滚动到顶部', e)
      this.triggerEvent('scrolltoupper', e)
    },
    // 下拉刷新执行
    _onRefresh: function (e) {
      console.log('下拉刷新执行', e)
      this.setData({
        pullState: 2
      })
      this.triggerEvent('onrefresh', e)
    },
    // 下拉刷新关闭
    _onClose: function (e) {
      console.log('下拉刷新关闭', e)
      this.triggerEvent('onrefreshclose', e);
    },
    // 滚动到底部
    _onLoadmore: function (e) {
      console.log('滚动到底部', e)
      this.triggerEvent('scrolltolower', e)
      if (!this.properties.refreshing) {
        this.triggerEvent('loadmore', e);
      }
    },
    // 回到顶部
    goTop() {
      console.log('到顶部')
      this.setData({
        topNum: 0
      })
    },
    // 滚动事件
    onScroll(e) {
      if (e.detail.scrollTop > 800) {
        if (this.data.showTop) return
        console.log('置顶按钮显示')
        this.setData({
          showTop: true
        })
      } else {
        if (!this.data.showTop) return
        console.log('置顶按钮隐藏')
        this.setData({
          showTop: false
        })
      }
      // 触发一个重绘的动作 否则经常不能横向切换
      this.triggerEvent('resizeactioin')
    },
    // 触摸结束事件
    handlerTouchend() {
      console.log('触摸结束')
      this.triggerEvent('resizeactioin')
    },
    // 隐藏置顶按钮
    hideGoTop() {
      console.log('隐藏 go top')
      this.setData({
        showTop: false
      })
    }
  },
})
 
然后index.json,无改动
{
  "usingComponents": {}
}
 
最后使用方法
 在要用到此组件的页面里面引入,首先index.json文件里面如下:
{
  "usingComponents": {
    "pull-refresh": "这里是组件文件所在路径"
  }
}
 
然后在index.wxml文件中引入index.json中命名的组件
<pull-refresh height="100vh" bindonrefresh="onRefresh" refreshing="{{ isRefreshing }}" bindloadmore="onLoadMore" nomore="{{ isFinish }}" pullText="下拉可以刷新" loadmoreText="加载中...">
  <view class="item" wx:for="{{ list }}" wx:key="index">{{ index+1 }}</view>
</pull-refresh>
 
最后在index.js文件中写你需要的逻辑,下面是我写的栗子代码
Page({
  data: {
    list: 0,
    pageInfo: {
      pageSize: 10,
      pageNum: 1
    },
    isRefreshing: false, // 下拉刷新
    isFinish: false // 上拉加载
  },
  onLoad() {
    this.getlist()
  },
  getlist() {
    const that = this
    setTimeout(() => {
      this.setData({
        list: that.data.list + that.data.pageInfo.pageSize,
        isFinish: that.data.list + that.data.pageInfo.pageSize >= 20,
        isRefreshing: false
      })
    }, 700)
    console.log('66666666', this.data.list)
  },
  // 上拉加载
  onLoadMore() {
    console.log("上拉加载", this.data.nomore)
    if (this.data.isFinish) return
    this.setData({
      isRefreshing: true
    })
    this.data.pageInfo.pageNum++
    this.getlist()
  },
  // 下拉刷新
  onRefresh() {
    console.log("下拉刷新")
    this.data.list = 0
    this.data.pageInfo = {
      pageSize: 10,
      pageNum: 1
    }
    this.getlist()
  }
})
 
样式文件index.wxss
.item{
  height: 160rpx;
  line-height: 160rpx;
  padding: 10px;
  text-align: center;
  background: linear-gradient(to bottom right, rgba(23, 184, 196, 0.295) ,rgba(0, 0, 255, 0.308));
}
 
到这里就宣布结束
 人无完人,码无完码,有缺陷之处希望各位不吝指出
 加个关注,不定时更新~
有玩王者的小伙伴也可以加我一起开黑哈
又菜又爱玩,我在安卓微区

 有兴趣的小伙伴可加我微
 

















