小程序可以利用<scroll-view>实现自定义的下拉刷新和上拉加载效果,但是有一个坑,<scroll-view>中不能出现fixed元素,要注意
废话不多说,直接上代码,可以根据具体需求去做调整。效果还是可以的~
实际项目中应用过.
如果不追求样式的变化的话,我是觉得原生的很好用~
scrollList.js
const STATS = {
init: '',
pulling: 'pulling',
enough: 'pulling enough',
refreshing: 'refreshing',
refreshed: 'refreshed',
reset: 'reset',
loading: 'loading',
}
let lastY = null; //记录上一次滚动的位置
let scrollTop = 0;
Component({
properties: {
leftList: [],
rightList: [],
feed: []
},
data: {
onRefresh: true,
loaderState: STATS.init,
pullHeight: 0,
progressed: 0,
pullDownHeight: 0,
animate: {},
//scroll-view的纵向是否滚动
scrollY: true,
},
properties: {
height: {
type: String
},
alreadyLoadData: {
type: Boolean,
value: true,
observer: function (e) {
this.isChange(e)
}
},
isEmpty: {
type: Boolean,
value: false
},
isScroll: {
type: Boolean,
value: false
}
},
methods: {
isChange: function (e) {
if (e) {
this.setData({
loaderState: STATS.refreshed
})
setTimeout(() => {
this.setData({
loaderState: STATS.reset,
pullDownHeight: 0
}, this.initSTATS)
}, 500);
}
},
initSTATS: function () {
setTimeout(() => {
this.setData({
loaderState: STATS.init
})
}, 500);
},
onScroll: function (e) {
scrollTop = e.detail.scrollTop
// console.log('组件scrollTop',scrollTop)
if(e.currentTarget.dataset.isscroll){
console.log('组件scrollTop',scrollTop)
var myEventDetail = {
scrollTop:e.detail.scrollTop
}
// 触发事件的选项
var myEventOption = {}
// 使用 triggerEvent 方法触发自定义组件事件,指定事件名、detail对象和事件选项
this.triggerEvent('onScroll',myEventDetail,{})
}
},
isEnd: function () {
this.triggerEvent('loadMore')
},
calculateDistance: function (touch) {
return touch.clientY - this._initialTouch.clientY;
},
touchStart: function (e) {
//记录滑动开始的Y坐标
let clientY = e.touches[0].clientY
lastY = clientY;
if (!this.canRefresh()) return;
if (e.touches.length == 1) {
this._initialTouch = {
clientY: e.touches[0].clientY,
scrollTop: scrollTop
};
}
},
touchMove: function (e) {
//根据滑动位置及滑动方向 更新scrollY
//下拉刷新时使用scrollY-false禁止组件滚动的原因:防止iOS系统下拉过程中scrollTop瞬间闪动到0 导致页面闪动
let clientY = e.touches[0].clientY,
scrollY = this.data.scrollY;
let newScrollY = false;
if ((clientY - lastY) > 0 && scrollTop <= 0) {//在页面顶部 进行下拉操作
newScrollY = false
} else {
newScrollY = true
}
if (scrollY !== newScrollY) {
this.setData({
scrollY: newScrollY
})
}
if (!this.canRefresh() || scrollTop > 0) return;
var calculateDistanceVal = this.calculateDistance(e.touches[0]);
var distance = calculateDistanceVal >= 150 ? 150 : calculateDistanceVal;
if (distance > 0 && scrollTop <= 5) {
var pullDistance = distance - this._initialTouch.scrollTop;
if (pullDistance < 0) {
pullDistance = 0;
this._initialTouch.scrollTop = distance;
}
// var pullHeight = this.easing(pullDistance);
var pullHeight = pullDistance / 4;
this.setData({
loaderState: pullHeight > 30 ? STATS.enough : STATS.pulling,
pullDownHeight: pullHeight
});
}
},
touchEnd: function (e) {
//滑动结束后 列表应该是可滚动的状态
this.setData({
scrollY: true
})
if (!this.canRefresh()) return;
// if (this.data.ifScroll > 0) return;
var endState = {
loaderState: STATS.reset,
pullDownHeight: 0
};
if (this.data.loaderState == STATS.enough) {
this.setData({
loaderState: STATS.refreshing,
});
setTimeout(() => {
this.triggerEvent('onRefresh')
}, 300);
} else {
this.setData(endState)
}
},
easing: function (distance) {
// t: current time, b: begInnIng value, c: change In value, d: duration
var t = distance;
var b = 0;
var d = 170; // 允许拖拽的最大距离
var c = d / 2.5; // 提示标签最大有效拖拽距离
return c * Math.sin(t / d * (Math.PI / 2)) + b;
},
canRefresh: function () {
let {
onRefresh,
loaderState
} = this.data
return onRefresh && [STATS.refreshing, STATS.loading].indexOf(loaderState) < 0;
},
}
})
scrollList.json
{
"component": true,
"usingComponents": {
}
}
scrollList.wxml
<scroll-view style="height:{{height}}" scroll-y="{{scrollY}}" upper-threshold="0" lower-threshold="200"
enable-back-to-top="true" class="tloader state-{{loaderState}}" bindscroll="onScroll" data-isscroll="{{isScroll}}" bindscrolltolower="isEnd"
bindtouchstart="touchStart" bindtouchend="touchEnd">
<view class="tloader-symbol">
<view class="tloader-msg">
<!-- <image class="img"></image> -->
<text/>
</view>
<view class="tloader-loading">
<!-- <text class="ui-loading" /> -->
<image src="../../images/gray-loading.png" class="ui-loading-img"></image>
</view>
</view>
<view class="tloader-body" bindtouchmove="touchMove" style="transform: translate3D(0,{{pullDownHeight+'px'}},0)">
<slot wx:if="{{!isEmpty}}"></slot>
<view class="empty" wx:else>
<!-- <view class="icon-empty" /> -->
<van-loading type="spinner" size="120rpx" color="#c9c9c9" />
<view>
<text>暂时没有数据</text>
</view>
</view>
</view>
</scroll-view>
scrollList.wxss
.tloader-msg:after {
content: '下拉刷新';
}
.state-reset .tloader-msg:after {
content: '';
}
.state-pulling.enough .tloader-msg:after {
content: '释放更新';
}
.state-refreshed .tloader-msg:after {
/\* content: '刷新成功'; \*/
content: '';
}
.tloader-loading:after {
content: '正在加载...';
}
.tloader-symbol .tloader-loading:after {
content: '加载更新';
}
.tloader-btn:after {
content: '点击加载更多';
}
.tloader {
position: relative;
overflow-y: scroll;
\-webkit-overflow-scrolling: touch;
}
/\* .tloader.state-pulling {
overflow-y: hidden;
} \*/
.tloader-symbol {
position: absolute;
top: 0;
left: 0;
right: 0;
color: #B0B0CE;
text-align: center;
height: 30px;
/\* background-color: #FFF; \*/
overflow: hidden;
}
.state- .tloader-symbol,
.state-reset .tloader-symbol {
height: 0;
}
.state-reset .tloader-symbol {
transition: height 0s 0.2s;
}
.state-loading .tloader-symbol {
display: none;
}
.tloader-msg {
line-height: 30px;
font-size: 12px;
}
.state-pulling .tloader-msg text {
display: inline-block;
font-size: 12px;
margin-right: 8px;
vertical-align: middle;
height: 1em;
border-left: 1px solid;
position: relative;
transition: transform .3s ease;
}
.state-pulling .tloader-msg text:before,
.state-reset .tloader-msg text:before,
.state-pulling .tloader-msg text:after,
.state-reset .tloader-msg text:after {
content: '';
position: absolute;
font-size: .5em;
width: 1em;
bottom: 0px;
border-top: 1px solid;
}
.state-pulling .tloader-msg text:before,
.state-reset .tloader-msg text:before {
right: 1px;
transform: rotate(50deg);
transform-origin: right;
}
.state-pulling .tloader-msg text:after,
.state-reset .tloader-msg text:after {
left: 0px;
transform: rotate(-50deg);
transform-origin: left;
}
.state-pulling.enough .tloader-msg text {
transform: rotate(180deg);
}
.state-refreshing .tloader-msg {
height: 0;
opacity: 0;
}
/\* .state-refreshed .tloader-msg {
opacity: 1;
transition: opacity 1s;
}
.state-refreshed .tloader-msg text {
display: inline-block;
box-sizing: content-box;
vertical-align: middle;
margin-right: 10px;
font-size: 20px;
height: 1em;
width: 1em;
border: 1px solid;
border-radius: 100%;
position: relative;
}
.state-refreshed .tloader-msg text:before {
content: '';
position: absolute;
top: 3px;
left: 7px;
height: 12px;
width: 5px;
border: solid;
border-width: 0 1px 1px 0;
transform: rotate(40deg);
} \*/
.tloader-body {
margin-top: -1px;
padding-top: 1px;
}
.state-refreshing .tloader-body {
transform: translate3d(0, 60px, 0);
transition: transform 0.2s;
}
.state-reset .tloader-body {
transition: transform 0.2s;
}
.state-refreshing .tloader-footer {
display: none;
}
.tloader-footer .tloader-btn {
color: #B0B0CE;
font-size: .9em;
text-align: center;
line-height: 60px;
}
.state-loading .tloader-footer .tloader-btn {
display: none;
}
.tloader-loading {
display: none;
text-align: center;
line-height: 30px;
font-size: 12px;
color: #B0B0CE;
}
.tloader-loading .ui-loading {
font-size: 20px;
margin-right: .6rem;
}
.state-refreshing .tloader-symbol .tloader-loading,
.state-loading .tloader-footer .tloader-loading {
display: block;
}
@keyframes circle {
100% {
transform: rotate(360deg);
}
}
.ui-loading {
margin-top: 6px;
/\* display: inline-block;
vertical-align: middle;
font-size: 1.5rem;
width: 1em;
height: 1em;
border: 2px solid #9494b6;
border-top-color: #fff;
border-radius: 100%;
animation: circle .8s infinite linear; \*/
}
.ui-loading-img{
width:30rpx;
height:30rpx;
position: absolute;
bottom: 14rpx;
left: 290rpx;
display: inline-block;
/\* vertical-align: middle; \*/
animation: a 1s steps(12) infinite;
background-size: 100%;}
/\* background-image:url('data:image/png;base64,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'); } \*/
@keyframes a {
0% {
\-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
\-webkit-transform: rotate(1turn);
transform: rotate(1turn);
}
}
.empty{
color: #B0B0CE;
text-align: center;
margin: 0 auto;
padding: 100rpx 100rpx;
background-color: #FFF;
}
.icon-empty{
width: 120rpx;
height: 120rpx;
display: inline-block;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABACAYAAACndwGZAAABdUlEQVR4Xu2bwU3DQBBFZ4SbgAIoACiABjhZWrsBQgE0QBtJB163QAmkARqgCeRFzgFBnHX0Eafd56PXPvznN39Odstc4zg+p5Qecuel3A8h3J/K4rmAwzDs3P2xFAC5HCGEkwwA8wcw8xjdlm5M13Uv0iiVDuRcvuwonXux9PNfYGKMm5TSVemhc/maptm2bfsxnx+DeauhV1Y+/F0IYQ+YJaEsmK2ZXdc6StM0PfV9/74wplYgrGvhy7OuM7A8xvhpZhcCzCoencGkKpKKIQGzMko/jfne4yLgIh4fhmHv7jeHdX00SoABzFJyjMkMPmAAo+0EjMEYjNEIYIzGi47BGIzRCGCMxouOwRiM0QhgjMaLjsEYjNEIYIzGi47BGIzRCGCMxouOwRiM0QhgjMaLjsEYjNEIYIzGi47BGIzRCGCMxouOwRiM0QhgjMaLjsGY/zNml1I6/EVa4+XuGzO7nLPz90nGAMCsgHk1s6bG0VnL/AW4jKZ4roy8ugAAAABJRU5ErkJggg==) no-repeat;
/\* background-image:url('data:image/png;base64,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'); \*/
background-size: 100% 100%;
}
.img{
width:130rpx;
height:130rpx;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。