微信小程序scroll-view下拉刷新(附带下拉刷新效果)
背景
- 在微信小程序上如果使用了scroll-view ,是没办法通过页面上的onPulldownRefresh函数触发下拉刷新的(重点解决的问题)
- 如果小程序页面上有顶部栏导航栏之类的,在下拉刷新的时候会把顶部栏一起拖下
- 想自定义下拉刷新的效果
解决方法
- 核心思路:通过监听touchstart和touchend 事件来记录e.changedTouches[0].pageY,然后通过比较就能知道是不是下拉手势
- 基于以上思路,为了开发者方便使用,自己实现了一个下拉刷新组件
https://download.csdn.net/download/u012308481/11985615 - 下拉刷新效果如下图
组件调用方法
wxml
//json文件记得引入组件
<super-scroll-view bind:lower="mylower" height="{{height}}px" bind:pulldown="myOnPullDownRefresh">
<view>你的内容</view>
<view>你的内容</view>
<view>你的内容</view>
<view>你的内容</view>
<view>你的内容</view>
</super-scroll-view>
js
Page({
myOnPullDownRefresh: function (event) {
//模拟数据请求
setTimeout(() => {
event.detail.resolve();//告诉组件完成下拉刷新,如果超过5s不调用,下拉刷新效果也会消失
}, 1000)
},
mylow:function(){
//todo 这里会触发触底事件,可以处理自己的上拉加载逻辑
}
})
tips
- 可以设置不使用组件下拉刷新效果
<super-scroll-view ball="{{false}}" bind:lower="mylower" height="{{height}}px" bind:pulldown="myOnPullDownRefresh">
<view>你的内容</view>
<view>你的内容</view>
<view>你的内容</view>
<view>你的内容</view>
<view>你的内容</view>
</super-scroll-view>
源码
- js
const timeout = 5000;
Component({
/**
* 组件的属性列表
*/
properties: {
height:{
type:String,
value:'100%'
},
ball:{
type:Boolean,
value:true
}
},
/**
* 组件的初始数据
*/
data: {
isBusy:false,
status: 0, //0:正常状态 1:加载中状态
freshBall:{
maxBallTop: 80,//刷新球最大下拉距离
top:0,//刷新球位置
show:true,
isLoading:false,
deg:0,//旋转角度
step:1.8//步长
},
isTop: true,//是否在顶部
touchStartY: 0,//刚触碰屏幕时 距离顶部的距离
touchMoveHeight: 0 //触碰屏幕时 手指移动的距离
},
attached(options) {
console.log(options)
},
/**
* 组件的方法列表
*/
methods: {
touchstart(){
const query = wx.createSelectorQuery()
query.select('#s-scroll-view').scrollOffset()
query.exec(function (res) {
console.log(res)
})
},
bindscroll: function (e) {
//console.log(e)
let self = this;
self.setData({
isTop: false
})
},
touchStart: function (e) {
//console.log(e)
let self = this;
self.setData({
touchStartY: e.changedTouches[0] && e.changedTouches[0].pageY,
isTop: true
})
},
touchMove: function (e) {
//console.log(e)
let self = this;
let touchStartY = self.data.touchStartY;
let touchMoveY = e.changedTouches[0] && e.changedTouches[0].pageY;
self.setData({
touchMoveHeight: touchMoveY - touchStartY
})
if (!this.data.isBusy && this.data.isTop){
this.data.freshBall.top = this.data.touchMoveHeight > this.data.freshBall.maxBallTop * this.data.freshBall.step ? this.data.freshBall.maxBallTop : this.data.touchMoveHeight/this.data.freshBall.step;
this.data.freshBall.deg = this.getDegByHeight(this.data.freshBall.top);
this.data.freshBall.isLoading=false;
this.setData({freshBall:this.data.freshBall});
}
},
touchEnd: function (e) {
//console.log(e)
let self = this;
let isTop = self.data.isTop;
let touchStartY = self.data.touchStartY;
let touchEndY = e.changedTouches[0] && e.changedTouches[0].pageY;
//console.log(isTop)
//console.log(touchStartY)
//console.log(touchEndY)
if (!this.data.isBusy && isTop && touchEndY-this.data.freshBall.maxBallTop*this.data.freshBall.step > touchStartY ) {
console.log("触发下拉刷新");
this.data.freshBall.isLoading = true;
this.setData({ freshBall: this.data.freshBall });
let timeId = null;
new Promise((resolve,reject)=>{
this.triggerEvent('pulldown', {resolve});
timeId = setTimeout(()=>{
resolve();
},timeout)
}).then((data)=>{
if(!timeId)return;
clearTimeout(timeId);
timeId = null;
this.data.freshBall.top = 0;
this.setData({ freshBall: this.data.freshBall });
this.setData({ isBusy: false });
}).catch(e=>{
if (!timeId) return;
clearTimeout(timeId);
this.data.freshBall.top = 0;
this.setData({ freshBall: this.data.freshBall });
this.setData({ isBusy: false });
})
this.setData({isBusy:true,touchMoveHeight:0});
}else{
this.data.freshBall.top = 0;
this.setData({ freshBall: this.data.freshBall });
}
},
lower(e) {
//console.log(e)
if (!this.data.isBusy) {
console.log("触底")
this.triggerEvent('lower')
this.setData({ isBusy: true })
setTimeout(() => {
this.setData({ isBusy: false });
}, 500)
}
},
getDegByHeight(height){
let maxBallTop = this.data.freshBall.maxBallTop;
return height/maxBallTop*360;
},
resetFreshBall(){//重置刷新球
this.setData({
maxBallTop: 100,
top:0,
show:true,
deg:0,
isLoading:false,
step:2
});
}
}
})
- html
<view wx:if="{{ball}}" class="freshball" style="transform:translateY({{freshBall.top}}px);opacity:{{freshBall.top<=0?0:1}}">
<image class="freshImg" src="resources/freshball.svg" style="transform:rotate({{freshBall.deg}}deg);display:{{freshBall.isLoading?'none':'block'}}"></image>
<image style="display:{{!freshBall.isLoading?'none':'block'}}" class="loading" src="resources/loading.svg"></image>
</view>
<view class="superScrollView">
<scroll-view style="height:{{height}}" scroll-y bindscrolltolower="lower" bindscroll="bindscroll" bindtouchstart="touchStart" bindtouchend="touchEnd" catchtouchmove="touchMove">
<view class="scrollWrap">
<slot />
</view>
</scroll-view>
</view>
- css
.superScrollView{
height:100%;
}
.scrollView{
height:100%;
}
.scrollWrap{
min-height:100%;
}
.freshball{
background:white;
border-radius: 50%;
width:80rpx;
height:80rpx;
display:flex;
justify-content: center;
align-items: center;
position:fixed;
top:0;
left:50%;
margin-left:-40rpx;
margin-top:-80rpx;
box-sizing: border-box;
z-index:999;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
transition:all 0.3s;
overflow:hidden;
}
.freshball .freshImg{
width:67%;
height:67%;
display:block;
transform-origin: center center;
transition:all 0.1s;
}
.freshball .loading{
width:70%;
height:70%;
display:block;
transform-origin: center center;
animation:loading 1s infinite;
}
@keyframes loading
{
from {transform:rotate(0deg)}
to {transform: rotate(360deg)}
}