在开发小程序的时候通常会遇到上拉或者下拉刷新的功能需求,然而这个功能很多页面也都会用到,因此这里,把这个功能封装为组件,方便复用
我很直接,不多说,上代码
首先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));
}
到这里就宣布结束
人无完人,码无完码,有缺陷之处希望各位不吝指出
加个关注,不定时更新~
有玩王者的小伙伴也可以加我一起开黑哈
又菜又爱玩,我在安卓微区
有兴趣的小伙伴可加我微