以下是基于小程序wux框架中refresh组件拓展的小程序滚动切换页码组件。
用法
<page-list id="wux-refresher" elid="biddingList" isShowPage="{{false}}" bind:scrollbot="scrollbot" bind:refresh="onRefresh">
<template is="biddingList" data='{{biddingList}}'></template> <!--列表dom-->
</page-list>
id=“wux-refresher” 获取组件上下文
elid=“biddingList”
isShowPage="{{false}}" 是否显示页码
bind:scrollbot=“scrollbot” 触底加载
bind:refresh=“onRefresh” 上拉刷新
wxml
<scroll-view class="scroll-view" id="{{elid}}" scroll-y="{{true}}" bindscrolltolower="scrollbot" bindscroll="scroll" bindtouchstart="bindtouchstart" bindtouchmove="bindtouchmove" bindtouchend="bindtouchend">
<view style="{{ style }}" >
<view class="{{ classes.wrap }}">
<view class="{{ classes.content }}">
<view class="{{ classes.iconPulling }}">
<text class="{{ classes.pIcon }}"></text>
</view>
<view class="{{ classes.textPulling }}">{{ pullingText }}</view>
<view class="{{ classes.iconRefreshing }}">
<text class="{{ classes.rIcon }}"></text>
</view>
<view class="{{ classes.textRefreshing }}">{{ refreshingText }}</view>
</view>
</view>
<slot></slot>
<view class="{{ classes.lWrap }}">
<view class="{{ classes.lContent }}">
<text wx:if="{{noData === false}}" class="{{ classes.rIcon }}"></text>
<text class="wux-loader__text-loading" wx:if="{{noData === false && isShowLoadingText === true}}">{{loadingText}}</text>
<view wx:if="{{noData === true}}">
{{loadNoDataText}}
</view>
</view>
</view>
</view>
</scroll-view>
<view wx:if="{{isShowPage}}" style="text-align:center;">{{currentPage}}</view>
js
// componets/list/list.js
import computedBehavior from '../helpers/computedBehavior';
import classNames from '../helpers/classNames';
const defaultStyle = 'transition: transform .4s; transform: translate3d(0px, 0px, 0px) scale(1);'
Component({
behaviors: [computedBehavior],
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
/**
* 组件的属性列表
*/
properties: {
elid:{type:String,value:''}, //元素id
isShowPage:{type:Boolean,value:false},
isEnd:{type:Boolean,value:false},
list:{type:Array,value:[]},
scrollTop:{type:Number,value:0},
prefixCls: {
type: String,
value: 'wux-refresher',
},
pullingIcon: {
type: String,
value: '',
},
pullingText: {
type: String,
value: '下拉刷新',
},
refreshingIcon: {
type: String,
value: '',
},
refreshingText: {
type: String,
value: '正在刷新',
},
disablePullingRotation: {
type: Boolean,
value: false,
},
distance: {
type: Number,
value: 30,
},
prefixLCls: {
type: String,
value: 'wux-loader'
},
isShowLoadingText: {
type: Boolean,
value: false
},
loadingText: {
type: String,
value: '正在加载'
},
loadNoDataText: {
type: String,
value: '没有更多数据'
},
},
/**
* 组件的初始数据
*/
data: {
currentPage:1,//展示的页码
pageInfoList:{},
initPageInfoList:{},//记录初始时第一页的数据
pageInfoListCount:1,//有多少条元素
scrollTop:0,
/**下拉刷新相关 */
style: defaultStyle,
visible: false,
active: false,
refreshing: false,
tail: false,
lVisible: false,
noData: false, // 是否没有更多数据
windowHeight: 0, // 窗口高度
newContentHeight: 0, // 新节点内容高度
oldContentHeight: 0, // 旧节点内容高度
loading: false, // 判断是否正在加载
},
computed: {
classes() {
const {
prefixCls,
pullingText,
pullingIcon,
disablePullingRotation,
refreshingText,
refreshingIcon,
visible,
active,
refreshing,
tail,
prefixLCls,
loading,
noData,
} = this.data
const wrap = classNames(prefixCls, {
[`${prefixCls}--hidden`]: !visible,
[`${prefixCls}--visible`]: visible,
[`${prefixCls}--active`]: active,
[`${prefixCls}--refreshing`]: refreshing,
[`${prefixCls}--refreshing-tail`]: tail,
}) //如果是true则className push入classNames
const content = classNames(`${prefixCls}__content`, {
[`${prefixCls}__content--text`]: pullingText || refreshingText,
})
const iconPulling = classNames(`${prefixCls}__icon-pulling`, {
[`${prefixCls}__icon-pulling--disabled`]: disablePullingRotation,
})
const textPulling = `${prefixCls}__text-pulling`
const iconRefreshing = `${prefixCls}__icon-refreshing`
const textRefreshing = `${prefixCls}__text-refreshing`
const pIcon = pullingIcon || `${prefixCls}__icon--arrow-down`
const rIcon = refreshingIcon || `${prefixCls}__icon--refresher`
const lWrap = classNames(prefixLCls, {
[`${prefixLCls}--hidden`]: !loading,
[`${prefixLCls}--visible`]: loading,
[`${prefixLCls}--end`]: noData,
})
const lContent = `${prefixLCls}__content`
//console.log('vies',this.data.visible)
return {
wrap,
content,
iconPulling,
textPulling,
iconRefreshing,
textRefreshing,
pIcon,
rIcon,
lWrap,
lContent,
}
},
},
/**
* 组件的方法列表
*/
methods: {
_pageDataFormate: function(page,top,height){
let obj = {};
obj[page] = { scrollTop: top, scrollBottom: top + height, height: height };
return obj
},
/**
* 滑动到底部触发
*/
scrollbot:function(e){
// if (this.data.isEnd) return false;
let that = this
let view = wx.createSelectorQuery().in(this);
//console.log('scrollbot count',this.data.pageInfoListCount)
if (!this.data.loading){
view.select("#"+this.data.elid).fields({
scrollOffset: true
},function(rect){
let pageInfoList = that.data.pageInfoList;
//当前页面有多少个元素,即多少页
let pageInfoListCount = Object.keys(pageInfoList).length
//记录上一页的bottom就是当前页的top
pageInfoList[pageInfoListCount].scrollBottom = rect.scrollTop;
//重新计算每一页的高度
let height = pageInfoList[pageInfoListCount].scrollBottom - pageInfoList[pageInfoListCount].scrollTop;
//构造当前页的数据
let pageInfo = that._pageDataFormate(pageInfoListCount + 1,rect.scrollTop,height)
//合并页
Object.assign(pageInfoList,pageInfo);
that.setData({
pageInfoList,
pageInfoListCount: Object.keys(pageInfoList).length
})
}).exec()
// console.log(this.data.pageInfoList)
this.setData({
loading: true,
refreshing: false,
//oldContentHeight: newContentHeight
})
//this.triggerEvent('loadmore')
this.triggerEvent('scrollbot');
}
},
/**
* 滑动时触发主要改变页码
*/
scroll:function(e){
//if (e.detail.scrollTop<0) return;
//console.log('scroll',e.detail.scrollTop)
let pageInfoList = this.data.pageInfoList;
let scrollTop = e.detail.scrollTop;
let currentPage = this.data.currentPage;
let pageInfoListCount = this.data.pageInfoListCount;
// console.log('scrollTop',scrollTop);
// console.log('scroll count', pageInfoListCount)
//判断如果当前只有一页则跳过
if (pageInfoListCount == 1) return false;
//如果当前滑动top大于当前页的bottom,则页码加一
if (pageInfoList[currentPage] && scrollTop > pageInfoList[currentPage].scrollBottom){
currentPage = (currentPage + 1) > pageInfoListCount ? pageInfoListCount : currentPage + 1
this.setData({
currentPage: currentPage,
//scrollTop: scrollTop
})
}
//如果当前滑动top少于当前页的top,则页码减一
else if (pageInfoList[currentPage] && currentPage > 1 && scrollTop < pageInfoList[currentPage].scrollTop ) {
this.setData({
currentPage: currentPage - 1,
//scrollTop: scrollTop
})
}
},
/****************下拉刷新功能************************ */
/**
* 显示
*/
activate() {
this.setData({
style: defaultStyle,
visible: true,
})
},
/**
* 隐藏
*/
deactivate() {
if (this.activated) this.activated = false
this.setData({
style: defaultStyle,
visible: false,
active: false,
refreshing: false,
tail: false,
})
},
/**
* 正在刷新
*/
refreshing() {
this.setData({
style: 'transition: transform .4s; transform: translate3d(0, 60px, 0) scale(1);',
visible: true,
active: true,
refreshing: true,
// 刷新时重新初始化加载状态
loading: false,
noData: false,
newContentHeight: 0,
oldContentHeight: 0,
lVisible: false,
})
},
/**
* 刷新后隐藏动画
*/
tail() {
this.setData({
visible: true,
active: true,
refreshing: true,
tail: true,
})
},
/**
* 加载后隐藏动画
*/
hide() {
this.setData({
lVisible: false,
})
},
/**
* 正在下拉
* @param {Number} diffY 距离
*/
move(diffY) {
const style = `transition-duration: 0s; transform: translate3d(0, ${diffY}px, 0) scale(1);`
const className = diffY < this.data.distance ? 'visible' : 'active'
this.setData({
style,
[className]: true,
})
},
/**
* 判断是否正在刷新
*/
isRefreshing() {
return this.data.refreshing
},
/**
* 判断是否正在加载
*/
isLoading() {
return this.data.loading
},
/**
* 获取触摸点坐标
*/
getTouchPosition(e) {
return {
x: e.changedTouches[0].pageX,
y: e.changedTouches[0].pageY,
}
},
/**
* 创建定时器
*/
requestAnimationFrame(callback) {
let currTime = new Date().getTime()
let timeToCall = Math.max(0, 16 - (currTime - this.lastTime))
let timeout = setTimeout(() => {
callback.bind(this)(currTime + timeToCall)
}, timeToCall)
this.lastTime = currTime + timeToCall
return timeout
},
/**
* 清空定时器
*/
cancelAnimationFrame(timeout) {
clearTimeout(timeout)
},
/**
* 下拉刷新完成后的函数
*/
finishPullToRefresh() {
this.setData({
pageInfoList:this.data.initPageInfoList,
})
setTimeout(() => {
this.requestAnimationFrame(this.tail)
setTimeout(() => this.deactivate(), 200)
}, 200)
},
/**
* 上拉加载完成后的函数
*/
finishLoadmore(bool) {
if (bool === true) {
setTimeout(() => {
this.setData({
noData: true,
loading: false,
})
}, 200)
} else {
setTimeout(() => {
this.setData({
loading: false
})
this.requestAnimationFrame(this.hide)
setTimeout(() => this.deactivate(), 200)
}, 200)
}
},
/**
* 手指触摸动作开始
*/
bindtouchstart(e) {
// console.log('tt', this.isRefreshing() || this.isLoading())
if (this.isRefreshing() || this.isLoading() ) return false
const p = this.getTouchPosition(e)
this.start = p
this.diffX = this.diffY = 0
this.activate()
},
/**
* 手指触摸后移动
*/
bindtouchmove(e) {
// console.log('tet', this.isRefreshing() || this.isLoading())
if (!this.start || this.isRefreshing() || this.isLoading() ) return false
const p = this.getTouchPosition(e)
this.diffX = p.x - this.start.x
this.diffY = p.y - this.start.y
// console.log(this.diffY);
if (this.diffY < 0) {
return false;
}
this.diffY = Math.pow(this.diffY, 0.8);
// console.log(this.diffY);
if (!this.activated && this.diffY > this.data.distance) {
this.activated = true
this.triggerEvent('pulling')
} else if (this.activated && this.diffY < this.data.distance) {
this.activated = false
}
this.move(this.diffY)
},
/**
* 手指触摸动作结束
*/
bindtouchend(e) {
this.start = false
if (this.diffY <= 0 || this.isRefreshing() || this.isLoading()) return false
// console.log(this.diffY,'dd')
this.deactivate()
if (Math.abs(this.diffY) >= this.data.distance) {
this.refreshing()
this.triggerEvent('refresh')
}
},
},
/**
* 生命周期
*/
lifetimes: {
created() {
this.lastTime = 0
this.activated = false
},
attached() {
let that = this
wx.getSystemInfo({
success: function (res) {
that.setData({
windowHeight: res.windowHeight
})
}
});
},
/**
* 初始化列表高度
*/
ready() {
let that = this;
let query = wx.createSelectorQuery().in(this);//初始时紧选区页面范围的节点,不会选取任何自定义组件中的组件
let id = "#" + this.data.elid
query.select(id).boundingClientRect(function (rect) {
let pageInfoList = that._pageDataFormate(1, 0,rect.height);
that.setData({
pageInfoList,
initPageInfoList:pageInfoList,
})
}).exec();
},
},
})
wxss
/*@import "../../colorui/main.wxss";
@import "../../colorui/icon.wxss";*/
.scroll-view{
width: 100%;
height: 100%;/*动态高度*/
}
.wux-refresher {
position: absolute;
top: -120rpx;
right: 0;
left: 0;
overflow: hidden;
margin: auto;
height: 100rpx
}
.wux-refresher--hidden {
visibility: hidden
}
.wux-refresher--visible {
visibility: visible
}
.wux-refresher__content {
position: absolute;
bottom: 10rpx;
left: 0;
width: 100%;
color: #666;
text-align: center;
font-size: 60rpx
}
.wux-refresher__content--text {
bottom: 0
}
.wux-refresher__text-pulling {
font-size: 32rpx;
line-height: 32rpx
}
.wux-refresher__text-refreshing {
font-size: 32rpx;
line-height: 32rpx;
display: none
}
.wux-refresher__icon-pulling {
width: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-style: preserve-3d;
padding: 14rpx 0;
animation-name: refresh-spin-back;
animation-duration: .2s;
animation-timing-function: linear;
animation-fill-mode: none;
transform: translateZ(0) rotate(0)
}
.wux-refresher__icon-refreshing {
width: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-style: preserve-3d;
padding: 14rpx 0;
display: none;
animation-duration: 1.5s
}
.wux-refresher__icon--arrow-down {
display: block;
margin: 0 auto;
width: 40rpx;
height: 40rpx;
background-repeat: no-repeat;
background-position: center center;
background-size: 40rpx 40rpx;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfhAhQABBG6qxTZAAABVklEQVRo3u3YvUoDQRiF4ePPTZgbEGsh23sBgqWCKQe8AVvB0tJySgPah9T2CaSxkNhY6i3YaRN1J7uz+31nAmnOVJNkM++zCcyyC2hobHnslHw5HOIZA3zgJL6xa+wX8ZcAgAGW/InsFgE2MAQQQAABBBBAAAEEEEAAAQQQYOuAjtvzcIprfOImvpYEwhFucYC7OGn/PHtfH87xuJpWcZ455vt3FjPrhCFmq+lFfGo7Iv8X3P/NZmFInv1/vr6eEfBVm1OEJJ+uZwJcJa/chLX8+nr9gDjFiCc08qM49f4CiGOW0JIf547t3Ac4giffuxH5Cb68YSf0Ebx501ZsJ/jzxmuBjcDkzRejfgKXdz2sDpd4SN6oasmKyzufljcIuWHOA3sewOLl+B1nm8w7ASaCK+8G9BKceQLQSXDnKUCWQORJQCuBytOABoHMFwASAp3X0AB+AFVjfoii8X5lAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTAyLTIwVDAwOjA0OjE3KzA4OjAwenpBPAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wMi0yMFQwMDowNDoxNyswODowMAsn+YAAAAAASUVORK5CYII=)
}
.wux-refresher__icon--refresher {
display: block;
margin: 0 auto;
width: 40rpx;
height: 40rpx;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%236c6c6c'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background-repeat: no-repeat;
background-position: 50%;
background-size: 100%;
transform-origin: 50%;
animation: refresh-spin-rotate 1s steps(12,end) infinite
}
.wux-refresher--active.wux-refresher--refreshing {
transition: transform .2s;
transform: scale(1)
}
.wux-refresher--active.wux-refresher--refreshing .wux-refresher__icon-pulling,
.wux-refresher--active.wux-refresher--refreshing .wux-refresher__text-pulling {
display: none
}
.wux-refresher--active.wux-refresher--refreshing .wux-refresher__icon-refreshing,
.wux-refresher--active.wux-refresher--refreshing .wux-refresher__text-refreshing {
display: block
}
.wux-refresher--active.wux-refresher--refreshing .wux-refresher--refreshing-tail {
transform: scale(0)
}
.wux-refresher--active .wux-refresher__icon-pulling:not(.wux-refresher__icon-pulling--disabled) {
animation-name: refresh-spin;
transform: translateZ(0) rotate(-180deg)
}
.wux-loader {
overflow: hidden;
margin: auto;
height: 100rpx;
font-size: 30rpx;
position: relative;
text-align: center;
display: none
}
.wux-loader .wux-refresher__icon--refresher {
display: inline-block;
margin: 0
}
.wux-loader__text-loading {
margin-left: 10rpx
}
.wux-loader--hidden {
visibility: hidden;
display: none
}
.wux-loader--visible {
visibility: visible;
display: block
}
.wux-loader--end {
visibility: visible;
display: block
}
.wux-loader__content {
position: absolute;
width: 100%;
top: 50%;
transform: translateY(-50%);
color: #666;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: center;
justify-content: center
}
@keyframes refresh-spin {
0% {
transform: translateZ(0) rotate(0)
}
to {
transform: translateZ(0) rotate(180deg)
}
}
@keyframes refresh-spin-back {
0% {
transform: translateZ(0) rotate(180deg)
}
to {
transform: translateZ(0) rotate(0)
}
}
@keyframes refresh-spin-rotate {
100% {
transform: rotate(360deg)
}
}