小程序可以利用<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;}