7 changed files with 1259 additions and 0 deletions
@ -0,0 +1,14 @@
|
||||
## 2.0.1(2023-10-25) |
||||
修复无法点击元素的bug |
||||
## 2.0.0(2023-10-24) |
||||
实现兼容微信小程序,app,H5。实现滚动到指定位置,实现指定滚动方向 |
||||
## 2.0(2023-10-24) |
||||
实现滚动到指定元素,实现滚动方向 |
||||
## 1.2.1(2023-08-23) |
||||
更新说明 |
||||
## 1.2(2023-08-18) |
||||
更新说明 |
||||
## 1.1(2023-08-18) |
||||
支持下拉刷新,回到顶部 |
||||
## 1.0(2023-08-17) |
||||
上线 |
@ -0,0 +1,855 @@
|
||||
<template> |
||||
<view ref="listtop" id="listtop"> |
||||
<scroll-view :scroll-top="scrollTop" :scroll-y="scrollY" class="scroll-view scroll-Y" @scrolltoupper="upper" |
||||
@scrolltolower="lower" @scroll="scroll" :scroll-into-view="scrollView" :lower-threshold="30" |
||||
id="scroll-view"> |
||||
<view class="scrolls"> |
||||
<view v-if="refresherm && JzscrollTop>10"> |
||||
<view v-if="triggeredType==0" :class="direction=='bottom'?'refreshermB':'refresherm'" |
||||
:style="'height:' + JzscrollTop + 'px'">{{direction=='bottom'?'下拉刷新':'上拉刷新'}}</view> |
||||
<view v-if="triggeredType==1" :class="direction=='bottom'?'refreshermB':'refresherm'" |
||||
:style="'height:' + JzscrollTop + 'px'">松开刷新</view> |
||||
<view v-if="triggeredType==2" :class="direction=='bottom'?'refreshermB':'refresherm'" |
||||
:style="'height:' + JzscrollTop + 'px'"> |
||||
<loading></loading> |
||||
</view> |
||||
<view v-if="triggeredType==3" :class="direction=='bottom'?'refreshermB':'refresherm'" |
||||
:style="'height:' + JzscrollTop + 'px'">刷新成功</view> |
||||
</view> |
||||
<view ref="listx" id="listx" @touchstart="touchstart3" @touchmove="touchmove3" @touchend="touchend3"> |
||||
<view class="ball" v-if="direction=='bottom'" :style="'height:' + heights + 'px'"></view> |
||||
<view v-if="list2 && list2.length > 0" ref="listItem" id="listItem"> |
||||
<slot :list="list2"></slot> |
||||
</view> |
||||
<view class="ball" v-if="direction=='top'" :style="'height:' + heights + 'px'"></view> |
||||
</view> |
||||
</view> |
||||
</scroll-view> |
||||
<div class="_backtop" v-if="isBackTop && direction=='bottom'" @click.stop="toTop"> |
||||
<view v-if="old.scrollTop > 100"> |
||||
<transition name="fade" appear> |
||||
<div class="top"> |
||||
<image src="./top.png"></image> |
||||
</div> |
||||
</transition> |
||||
</view> |
||||
</div> |
||||
</view> |
||||
</template> |
||||
<!-- |
||||
现有问题如下: |
||||
1、因为小程序局限问题,移动到端指定元素无法使用组件的形式,且小程序需要定义滚动组件的高度,所以建议小程序复制代码后操作 |
||||
2、当用户疯狂滑动页面,可能会导致页面白屏问题 |
||||
--> |
||||
<script> |
||||
import loading from './loading.vue' |
||||
let observer = null; |
||||
let scrollTop1 = null; |
||||
let scrollTop2 = null; |
||||
export default { |
||||
components: { |
||||
loading |
||||
}, |
||||
data() { |
||||
return { |
||||
scrollY: true, |
||||
scrollClientY: 0, |
||||
JzscrollTop: 0, |
||||
refresherm: false, |
||||
triggeredType: 0, |
||||
scrollTop: 99999, |
||||
old: { |
||||
scrollTop: 0 |
||||
}, |
||||
list2: [], |
||||
clientHeight: 0, |
||||
heights: 0, |
||||
heightt: 0, |
||||
lowers: true, |
||||
copyList: [], |
||||
listPage: [], |
||||
page: 0, |
||||
scrollView: '', |
||||
noLower: true, |
||||
virtualHeight: 0 |
||||
}; |
||||
}, |
||||
props: { |
||||
direction: { |
||||
// 加载分页默认20 |
||||
type: String, |
||||
default: 'bottom' |
||||
}, |
||||
refresher: { |
||||
// 是否拥有下拉刷新 |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
pageSizes: { |
||||
// 加载分页默认20 |
||||
type: Number, |
||||
default: 20 |
||||
}, |
||||
code: { |
||||
// 加载分页默认20 |
||||
type: String, |
||||
default: 'id' |
||||
}, |
||||
data: { |
||||
//数据 |
||||
type: Array, |
||||
default () { |
||||
return []; |
||||
} |
||||
}, |
||||
isBackTop: { |
||||
//是否拥有回到顶部 |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
}, |
||||
onUnload() { |
||||
if (observer) { |
||||
observer.disconnect() |
||||
} |
||||
}, |
||||
created() { |
||||
if (this.direction == 'top') { |
||||
this.list = JSON.parse(JSON.stringify(this.data)); |
||||
this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes); |
||||
this.getList(); |
||||
} else { |
||||
this.scrollTop = 0 |
||||
this.list = JSON.parse(JSON.stringify(this.data)); |
||||
this.listPage = this.splitArrayIntoGroups2(this.list, this.pageSizes); |
||||
this.list2 = this.listPage[0] |
||||
} |
||||
}, |
||||
mounted() { |
||||
observer = uni.createIntersectionObserver(this); |
||||
observer.relativeTo('.scroll-view', { |
||||
bottom: 100 |
||||
}).observe('.ball', (res) => { |
||||
if (res.intersectionRatio > 0) { |
||||
let that = this |
||||
if (this.page > 0 && this.heights > 0) { |
||||
if (this.direction == 'top') { |
||||
that.scrollY = false |
||||
this.page--; |
||||
this.list2.splice(0, this.pageSizes); |
||||
this.list2 = [...this.list2, ...this.listPage[this.page - 1]]; |
||||
this.$nextTick(() => { |
||||
this.heights = this.heightList[this.page - 1]; |
||||
that.scrollY = true |
||||
}) |
||||
} else { |
||||
that.scrollY = false |
||||
this.page--; |
||||
this.list2.splice(this.list2.length - this.pageSizes, that.pageSizes); |
||||
this.list2 = [...this.listPage[this.page - 1], ...this.list2] |
||||
this.$nextTick(() => { |
||||
this.heights = this.heightList[this.page - 1]; |
||||
that.scrollY = true |
||||
}) |
||||
|
||||
} |
||||
} |
||||
} |
||||
}) |
||||
// #ifdef MP-WEIXIN || APP |
||||
const query = uni.createSelectorQuery().in(this); |
||||
query.select('#listtop').boundingClientRect(data => { |
||||
this.clientHeight = data.height |
||||
}).exec(); |
||||
// #endif |
||||
// #ifdef H5 |
||||
this.clientHeight = this.$refs.listtop.$el.clientHeight |
||||
// #endif |
||||
|
||||
}, |
||||
watch: { |
||||
data() { |
||||
// 数据变动之后初始化数据 |
||||
if (this.direction == 'top') { |
||||
this.page = 0; |
||||
this.heights = 0; |
||||
this.heightList = [0]; |
||||
this.list = JSON.parse(JSON.stringify(this.data)); |
||||
this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes); |
||||
this.getList(); |
||||
} else { |
||||
this.page = 0; |
||||
this.heights = 0; |
||||
this.heightList = [0]; |
||||
this.list = JSON.parse(JSON.stringify(this.data)); |
||||
this.listPage = this.splitArrayIntoGroups2(this.list, this.pageSizes); |
||||
this.list2 = this.listPage[0] |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
toTop() { |
||||
if (this.direction == 'top') { |
||||
|
||||
} else { |
||||
let that = this |
||||
let scrollTops = this.old.scrollTop |
||||
const duration = 1000 // duration 默认为 1000ms |
||||
const easing = (t) => t * (2 - t) // 缓动函数 |
||||
let start = scrollTops // 起始位置 |
||||
let end = 0 // 结束位置 |
||||
let change = end - start // 变化量 |
||||
let currentTime = 0 // 当前时间 |
||||
// 滚动动画函数 |
||||
const animateScroll = function() { |
||||
currentTime += 16 // 设定定时器暂停时间,16ms |
||||
let val = easing(currentTime / duration) * change + start |
||||
that.scrollTop = val |
||||
if (currentTime < duration) { |
||||
setTimeout(animateScroll, 16) |
||||
} |
||||
} |
||||
animateScroll() |
||||
} |
||||
|
||||
}, |
||||
addPage(list) { |
||||
if (this.direction == 'top') { |
||||
let id = list[0][this.code] |
||||
let li = list.reverse() |
||||
this.listPage.push(li) |
||||
this.upper() |
||||
this.scrollView = null |
||||
let that = this |
||||
this.$nextTick(function() { |
||||
this.scrollView = id |
||||
}); |
||||
} else { |
||||
let id = list[0][this.code] |
||||
let li = list |
||||
this.listPage.push(li) |
||||
this.lower() |
||||
this.scrollView = null |
||||
let that = this |
||||
this.$nextTick(function() { |
||||
this.scrollView = id |
||||
}); |
||||
} |
||||
}, |
||||
async toAssign(val, secl) { |
||||
this.noLower = false |
||||
let that = this |
||||
const query2 = uni.createSelectorQuery().in(this); |
||||
let obj = null |
||||
for (var i = 0; i < this.listPage.length; i++) { |
||||
let list = this.listPage[i] |
||||
for (var j = 0; j < list.length; j++) { |
||||
let item = list[j] |
||||
if (item[this.code] == val) { |
||||
obj = { |
||||
page: i + 1, |
||||
index: j, |
||||
id: val |
||||
} |
||||
break |
||||
} |
||||
} |
||||
if (obj) { |
||||
break |
||||
} |
||||
} |
||||
// 检查元素是否在当前dom |
||||
if (this.list2.some(el => { |
||||
return el[this.code] == val |
||||
})) { |
||||
this.scrollView = null |
||||
this.$nextTick(function() { |
||||
this.scrollView = val |
||||
}); |
||||
} else { |
||||
// 滚动到指定page |
||||
// 向上查找 |
||||
if (this.page < obj.page) { |
||||
|
||||
let c = (obj.page + this.page) >= this.listPage.length ? (obj.page - this.page - 1) : (obj |
||||
.page - this.page) |
||||
|
||||
if (this.heightList[this.page + c - 1]) { |
||||
if (this.direction == 'top') { |
||||
this.list2 = [...this.listPage[this.page + c], ...this.listPage[this.page + c - 1]] |
||||
} else { |
||||
this.list2 = [...this.listPage[this.page + c - 1], ...this.listPage[this.page + c]] |
||||
} |
||||
this.heights = this.heightList[this.page + c - 1]; |
||||
this.scrollView = '' |
||||
this.page = this.page + c |
||||
this.$nextTick(function() { |
||||
this.scrollView = val |
||||
// #ifdef MP-WEIXIN |
||||
const query = uni.createSelectorQuery().in(secl); |
||||
let id = '#' + val |
||||
query.select(id).boundingClientRect(data => { |
||||
if (data.top) { |
||||
that.scrollTop = data.top |
||||
} |
||||
}).exec(); |
||||
// #endif |
||||
}); |
||||
} else { |
||||
let pasc = that.page |
||||
for (let i = 0; i < c; i++) { |
||||
pasc++ |
||||
let a = await this.toUpper(pasc) |
||||
} |
||||
this.scrollView = '' |
||||
this.$nextTick(function() { |
||||
this.scrollView = val |
||||
setTimeout(() => { |
||||
that.noLower = true |
||||
}, 1000) |
||||
// #ifdef MP-WEIXIN |
||||
const query = uni.createSelectorQuery().in(secl); |
||||
let id = '#' + val |
||||
query.select(id).boundingClientRect(data => { |
||||
if (data.top) { |
||||
that.scrollTop = data.top |
||||
} |
||||
}).exec(); |
||||
// #endif |
||||
}); |
||||
} |
||||
} else { |
||||
let c = this.page - obj.page |
||||
for (let i = 0; i < c; i++) { |
||||
let a = await this.tolower() |
||||
} |
||||
this.scrollView = '' |
||||
this.$nextTick(function() { |
||||
this.scrollView = val |
||||
setTimeout(() => { |
||||
that.noLower = true |
||||
}, 1000) |
||||
// #ifdef MP-WEIXIN |
||||
const query = uni.createSelectorQuery().in(secl); |
||||
let id = '#' + val |
||||
query.select(id).boundingClientRect(data => { |
||||
if (data.top) { |
||||
that.scrollTop = data.top |
||||
} |
||||
}).exec(); |
||||
// #endif |
||||
}); |
||||
} |
||||
} |
||||
}, |
||||
tolower: function() { //向上加载数据 |
||||
return new Promise(resolve => { |
||||
let that = this |
||||
if (this.direction == 'top') { |
||||
this.page--; |
||||
this.list2.splice(0, this.pageSizes); |
||||
this.list2 = [...this.list2, ...this.listPage[this.page - 1]]; |
||||
this.$nextTick(() => { |
||||
this.heights = this.heightList[this.page - 1]; |
||||
resolve() |
||||
}) |
||||
} else { |
||||
this.page--; |
||||
this.list2.splice(this.list2.length - this.pageSizes, this.pageSizes); |
||||
this.list2 = [...this.listPage[this.page - 1], ...this.list2] |
||||
this.$nextTick(() => { |
||||
this.heights = this.heightList[this.page - 1]; |
||||
resolve() |
||||
}) |
||||
|
||||
} |
||||
}) |
||||
}, |
||||
toUpper: function(pasc) { //向上加载数据 |
||||
return new Promise(resolve => { |
||||
let that = this |
||||
if (this.direction == 'top') { |
||||
if (this.listPage.length - 1 > this.page) { |
||||
this.scrollTop = this.old.scrollTop |
||||
this.$nextTick(function() { |
||||
this.scrollTop = 100 |
||||
}); |
||||
// #ifdef MP-WEIXIN || APP |
||||
const query = uni.createSelectorQuery().in(this); |
||||
query.select('#listx').boundingClientRect(data => { |
||||
this.page++ |
||||
if (!this.heightList[this.page]) { |
||||
this.heightList.push(data.height); |
||||
} |
||||
this.list2 = [...this.listPage[this.page], ...this.list2] |
||||
this.$nextTick(() => { |
||||
if (this.list2.length > this.pageSizes * 2) { |
||||
this.list2.splice(this.list2.length - this.pageSizes, |
||||
that.pageSizes); |
||||
this.heights = this.heightList[this.page - 1]; |
||||
} |
||||
setTimeout(() => { |
||||
resolve(1) |
||||
}, 80) |
||||
}) |
||||
}).exec(); |
||||
// #endif |
||||
// #ifdef H5 |
||||
let container = this.$refs.listx.$el |
||||
this.page++ |
||||
if (!this.heightList[this.page]) { |
||||
this.heightList.push(container.clientHeight); |
||||
} |
||||
this.list2 = [...this.listPage[this.page], ...this.list2] |
||||
this.$nextTick(() => { |
||||
if (that.list2.length > that.pageSizes * 2) { |
||||
that.list2.splice(that.list2.length - that.pageSizes, that |
||||
.pageSizes); |
||||
that.heights = that.heightList[that.page - 1]; |
||||
} |
||||
setTimeout(() => { |
||||
resolve(1) |
||||
}, 80) |
||||
}) |
||||
// #endif |
||||
} else { |
||||
resolve(1) |
||||
} |
||||
} else { |
||||
if (this.listPage.length - 1 > this.page) { |
||||
this.scrollTop = this.old.scrollTop |
||||
// #ifdef MP-WEIXIN || APP |
||||
this.$nextTick(function() { |
||||
that.page = pasc |
||||
const query = uni.createSelectorQuery().in(this); |
||||
query.select('#listx').boundingClientRect(data => { |
||||
if (!that.heightList[that.page]) { |
||||
that.heightList.push(data.height); |
||||
} |
||||
that.list2 = [...that.list2, ...that.listPage[that.page]] |
||||
that.$nextTick(() => { |
||||
if (that.list2.length > (that.listPage[that |
||||
.page].length + that.listPage[that |
||||
.page - 1].length)) { |
||||
that.list2.splice(0, that.pageSizes); |
||||
that.heights = that.heightList[that.page - |
||||
1]; |
||||
} |
||||
setTimeout(() => { |
||||
resolve(1) |
||||
}, 80) |
||||
}) |
||||
}).exec(); |
||||
}); |
||||
// #endif |
||||
// #ifdef H5 |
||||
let container = this.$refs.listx.$el |
||||
this.page++ |
||||
if (!this.heightList[this.page]) { |
||||
this.heightList.push(container.clientHeight); |
||||
} |
||||
this.list2 = [...this.list2, ...this.listPage[this.page]] |
||||
that.scrollTop = 0 |
||||
this.$nextTick(() => { |
||||
// 有问题 |
||||
if (that.list2.length > (that.listPage[this.page].length + that |
||||
.listPage[this.page - 1].length)) { |
||||
that.list2.splice(0, this.pageSizes); |
||||
that.heights = that.heightList[that.page - 1]; |
||||
that.scrollTop = 999999 |
||||
} |
||||
setTimeout(() => { |
||||
resolve(1) |
||||
}, 80) |
||||
}) |
||||
// #endif |
||||
} else { |
||||
resolve(1) |
||||
} |
||||
|
||||
} |
||||
}) |
||||
|
||||
}, |
||||
// 刷新 |
||||
refreshers() { |
||||
if (this.direction == 'top') { |
||||
this.triggeredType = 3 |
||||
let that = this |
||||
setTimeout(() => { |
||||
that.refresherm = false |
||||
that.JzscrollTop = 0 |
||||
that.goTop() |
||||
that.lowers = true |
||||
that.scrollY = true |
||||
}, 500) |
||||
} else { |
||||
this.triggeredType = 3 |
||||
let that = this |
||||
setTimeout(() => { |
||||
that.refresherm = false |
||||
that.JzscrollTop = 0 |
||||
that.lowers = true |
||||
that.scrollY = true |
||||
}, 500) |
||||
} |
||||
}, |
||||
|
||||
|
||||
touchend3(e) { //当手指从屏幕上离开的时候触发。 |
||||
if (this.direction == 'top') { |
||||
if(this.heights == 0 && (this.scrollTop == 99999 || this.lowers)){ |
||||
this.touchend(e) |
||||
} |
||||
}else{ |
||||
if(this.heights == 0 && (this.old.scrollTop == 0 || this.lowers)){ |
||||
this.touchend2(e) |
||||
} |
||||
} |
||||
}, |
||||
touchstart3(e) { //当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发。 |
||||
if (this.direction == 'top') { |
||||
if(this.heights == 0 && (this.scrollTop == 99999 || this.lowers)){ |
||||
this.touchstart(e) |
||||
} |
||||
}else{ |
||||
if(this.heights == 0 && (this.old.scrollTop == 0 || this.lowers)){ |
||||
this.touchstart2(e) |
||||
} |
||||
} |
||||
}, |
||||
touchmove3(e) { //当手指在屏幕上滑动的时候连续地触发。在这个事件发生期间,调用preventDefault()事件可以阻止滚动 |
||||
if (this.direction == 'top') { |
||||
if(this.heights == 0 && (this.scrollTop == 99999 || this.lowers)){ |
||||
this.touchmove(e) |
||||
} |
||||
}else{ |
||||
if(this.heights == 0 && (this.old.scrollTop == 0 || this.lowers)){ |
||||
this.touchmove2(e) |
||||
} |
||||
} |
||||
}, |
||||
|
||||
|
||||
// 上拉加载 |
||||
touchend(e) { //当手指从屏幕上离开的时候触发。 |
||||
if (this.heights == 0 && this.refresherm) { |
||||
if (this.JzscrollTop >= 80) { |
||||
this.triggeredType = 2 |
||||
this.JzscrollTop = 80 |
||||
this.$emit('refresher') |
||||
} else { |
||||
this.triggeredType = 0 |
||||
this.JzscrollTop = 0 |
||||
this.scrollY = true |
||||
} |
||||
} else { |
||||
this.scrollY = true |
||||
this.JzscrollTop = 0 |
||||
} |
||||
}, |
||||
touchstart(e) { //当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发。 |
||||
console.log(this.refresher) |
||||
if (this.refresher && this.heights == 0 && (this.scrollTop == 99999 || this.lowers)) { |
||||
this.refresherm = true |
||||
let pas = e.changedTouches[0] |
||||
this.scrollClientY = pas.pageY |
||||
this.JzscrollTop = 0 |
||||
} |
||||
}, |
||||
touchmove(e) { //当手指在屏幕上滑动的时候连续地触发。在这个事件发生期间,调用preventDefault()事件可以阻止滚动 |
||||
if (this.heights == 0 && this.refresherm && this.triggeredType != 2) { |
||||
let pas = e.changedTouches[0] |
||||
if (this.scrollClientY > pas.pageY) { |
||||
this.scrollY = false |
||||
// 上拉 |
||||
let y = (this.scrollClientY - pas.pageY) |
||||
this.JzscrollTop = y > 150 ? 150 : y |
||||
if (this.JzscrollTop >= 100) { |
||||
this.triggeredType = 1 |
||||
} else { |
||||
this.triggeredType = 0 |
||||
} |
||||
this.$forceUpdate() |
||||
} |
||||
} |
||||
}, |
||||
// 下拉刷新 |
||||
touchend2(e) { //当手指从屏幕上离开的时候触发。 |
||||
if (this.heights == 0 && this.refresherm) { |
||||
if (this.JzscrollTop >= 80) { |
||||
this.triggeredType = 2 |
||||
this.JzscrollTop = 80 |
||||
this.$emit('refresher') |
||||
} else { |
||||
this.triggeredType = 0 |
||||
this.JzscrollTop = 0 |
||||
this.scrollY = true |
||||
} |
||||
} else { |
||||
this.scrollY = true |
||||
this.JzscrollTop = 0 |
||||
} |
||||
}, |
||||
touchstart2(e) { //当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发。 |
||||
if (this.refresher && this.heights == 0 && (this.scrollTop == 0 || this.lowers)) { |
||||
this.refresherm = true |
||||
let pas = e.changedTouches[0] |
||||
this.scrollClientY = pas.pageY |
||||
this.JzscrollTop = 0 |
||||
|
||||
} |
||||
}, |
||||
touchmove2(e) { //当手指在屏幕上滑动的时候连续地触发。在这个事件发生期间,调用preventDefault()事件可以阻止滚动 |
||||
if (this.heights == 0 && this.refresherm && this.triggeredType != 2) { |
||||
let pas = e.changedTouches[0] |
||||
if (this.scrollClientY < pas.pageY) { |
||||
this.scrollY = false |
||||
// 上 |
||||
let y = (pas.pageY - this.scrollClientY) |
||||
this.JzscrollTop = y > 150 ? 150 : y |
||||
if (this.JzscrollTop >= 100) { |
||||
this.triggeredType = 1 |
||||
} else { |
||||
this.triggeredType = 0 |
||||
} |
||||
this.$forceUpdate() |
||||
} |
||||
} |
||||
}, |
||||
// 数据处理 |
||||
upper: function(e) { //到达顶部,开始加载数据 |
||||
if (!this.noLower) { |
||||
return |
||||
} |
||||
if (this.direction == 'top') { |
||||
if (this.listPage.length - 1 > this.page) { |
||||
console.log('到达顶部,开始加载数据') |
||||
let that = this |
||||
// #ifdef MP-WEIXIN || APP |
||||
const query = uni.createSelectorQuery().in(this); |
||||
query.select('#listx').boundingClientRect(data => { |
||||
this.page++ |
||||
if (!this.heightList[this.page]) { |
||||
this.heightList.push(data.height); |
||||
} |
||||
this.list2 = [...this.listPage[this.page], ...this.list2] |
||||
// #ifndef APP |
||||
this.$nextTick(() => { |
||||
if (this.list2.length > this.pageSizes * 2) { |
||||
this.list2.splice(this.list2.length - this.pageSizes, that |
||||
.pageSizes); |
||||
this.heights = this.heightList[this.page - 1]; |
||||
} |
||||
}) |
||||
// #endif |
||||
// #ifndef MP-WEIXIN |
||||
setTimeout(() => { |
||||
if (this.list2.length > this.pageSizes * 2) { |
||||
this.list2.splice(this.list2.length - this.pageSizes, that |
||||
.pageSizes); |
||||
this.heights = this.heightList[this.page - 1]; |
||||
} |
||||
}, 80) |
||||
// #endif |
||||
}).exec(); |
||||
// #endif |
||||
// #ifdef H5 |
||||
let container = this.$refs.listx.$el |
||||
this.page++ |
||||
if (!this.heightList[this.page]) { |
||||
this.heightList.push(container.clientHeight); |
||||
} |
||||
console.log(this.heightList, this.listPage) |
||||
this.list2 = [...this.listPage[this.page], ...this.list2] |
||||
this.$nextTick(() => { |
||||
// 有问题 |
||||
if (that.list2.length > (that.listPage[this.page].length + that.listPage[this |
||||
.page - 1].length)) { |
||||
that.list2.splice(that.list2.length - that.pageSizes, that.pageSizes); |
||||
that.heights = that.heightList[that.page - 1]; |
||||
} |
||||
}) |
||||
// #endif |
||||
} else { |
||||
this.$emit('scrolltoupper', this.page) |
||||
} |
||||
} else { |
||||
this.lowers = true |
||||
} |
||||
}, |
||||
lower: function(e) { //到达底部数据还原 |
||||
if (!this.noLower) { |
||||
return |
||||
} |
||||
if (this.direction == 'top') { |
||||
this.lowers = true |
||||
} else { |
||||
if (this.noLower && this.listPage.length - 1 > this.page) { |
||||
console.log('到达底部,开始加载数据') |
||||
let that = this |
||||
// #ifdef MP-WEIXIN || APP |
||||
const query = uni.createSelectorQuery().in(this); |
||||
query.select('#listx').boundingClientRect(data => { |
||||
this.page++ |
||||
if (!this.heightList[this.page]) { |
||||
this.heightList.push(data.height); |
||||
} |
||||
this.list2 = [...this.list2, ...this.listPage[this.page]] |
||||
// #ifndef APP |
||||
this.$nextTick(() => { |
||||
if (this.list2.length > this.pageSizes * 2) { |
||||
this.list2.splice(0, this.pageSizes); |
||||
this.heights = this.heightList[this.page - 1]; |
||||
} |
||||
}) |
||||
// #endif |
||||
// #ifndef MP-WEIXIN |
||||
setTimeout(() => { |
||||
if (this.list2.length > this.pageSizes * 2) { |
||||
this.list2.splice(0, this.pageSizes); |
||||
this.heights = this.heightList[this.page - 1]; |
||||
} |
||||
}, 80) |
||||
// #endif |
||||
}).exec(); |
||||
// #endif |
||||
// #ifdef H5 |
||||
let container = this.$refs.listx.$el |
||||
this.page++ |
||||
if (!this.heightList[this.page]) { |
||||
this.heightList.push(container.clientHeight); |
||||
} |
||||
this.list2 = [...this.list2, ...this.listPage[this.page]] |
||||
this.$nextTick(() => { |
||||
// 有问题 |
||||
if (that.list2.length > (that.listPage[this.page].length + that.listPage[this |
||||
.page - 1].length)) { |
||||
that.list2.splice(0, this.pageSizes); |
||||
that.heights = that.heightList[that.page - 1]; |
||||
} |
||||
}) |
||||
// #endif |
||||
} else { |
||||
this.$emit('scrolltoupper', this.page) |
||||
} |
||||
} |
||||
}, |
||||
scroll: function(e) { //滚动过程 |
||||
let that = this |
||||
if (e.detail.scrollHeight > (e.detail.scrollTop + this.clientHeight + 200)) { |
||||
this.lowers = false |
||||
} |
||||
this.old.scrollTop = e.detail.scrollTop |
||||
}, |
||||
goTop: function(e) { |
||||
// 解决view层不同步的问题 |
||||
this.scrollTop = this.old.scrollTop |
||||
this.$nextTick(function() { |
||||
this.scrollTop = 999999 |
||||
}); |
||||
}, |
||||
// *********** 加载操作 *************** |
||||
splitArrayIntoGroups(array, groupSize) { |
||||
// 数据处理 |
||||
const groups = []; |
||||
for (let i = 0; i < array.length; i += groupSize) { |
||||
groups.push(array.slice(i, i + groupSize).reverse()); |
||||
} |
||||
return groups; |
||||
}, |
||||
splitArrayIntoGroups2(array, groupSize) { |
||||
const groups = []; |
||||
for (let i = 0; i < array.length; i += groupSize) { |
||||
groups.push(array.slice(i, i + groupSize)); |
||||
} |
||||
return groups; |
||||
}, |
||||
getList() { |
||||
this.list2 = this.listPage[0] |
||||
this.$nextTick(function() { |
||||
this.goTop() |
||||
}); |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
._backtop { |
||||
position: absolute; |
||||
z-index: 1; |
||||
right: 30rpx; |
||||
bottom: 50px; |
||||
|
||||
.top { |
||||
width: 60px; |
||||
height: 30px; |
||||
box-shadow: 0.3rem 0.3rem 0.6rem rgba(0, 0, 0, .3), -0.2rem -0.2rem 0.5rem rgba(255, 255, 255, .3); |
||||
background: #fff; |
||||
border-radius: 50%; |
||||
padding: 15px 0; |
||||
text-align: center; |
||||
|
||||
image { |
||||
width: 30px; |
||||
height: 30px; |
||||
} |
||||
} |
||||
|
||||
/* 过渡类名 */ |
||||
.fade-enter-active, |
||||
.fade-leave-active { |
||||
transition: opacity 0.7s; |
||||
} |
||||
|
||||
.fade-enter, |
||||
.fade-leave-to |
||||
|
||||
/* .fade-leave-active below version 2.1.8 */ |
||||
{ |
||||
opacity: 0; |
||||
} |
||||
} |
||||
|
||||
.list_item { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
border: 1px solid #f5f5f5; |
||||
} |
||||
|
||||
.scrolls { |
||||
position: relative; |
||||
} |
||||
|
||||
.refreshermB { |
||||
text-align: center; |
||||
line-height: 60px; |
||||
height: 0px; |
||||
color: #999; |
||||
background-color: #fff; |
||||
} |
||||
|
||||
.refresherm { |
||||
text-align: center; |
||||
position: absolute; |
||||
bottom: 0; |
||||
left: 0; |
||||
right: 0; |
||||
line-height: 60px; |
||||
height: 0px; |
||||
color: #999; |
||||
z-index: 99; |
||||
background-color: #fff; |
||||
} |
||||
|
||||
.scroll-Y { |
||||
/* #ifndef MP-WEIXIN */ |
||||
height: 100%; |
||||
/* #endif */ |
||||
/* #ifdef MP-WEIXIN */ |
||||
height: calc(100vh - 80px); |
||||
/* #endif */ |
||||
position: relative; |
||||
} |
||||
</style> |
@ -0,0 +1,76 @@
|
||||
<template> |
||||
<div class="template"> |
||||
<view class="cartoon" > |
||||
<span class="char">拼</span> |
||||
<span class="char">命</span> |
||||
<span class="char">加</span> |
||||
<span class="char">载</span> |
||||
<span class="char">中</span> |
||||
<span class="char">.</span> |
||||
<span class="char">.</span> |
||||
<span class="char">.</span> |
||||
</view> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.cartoon{ |
||||
.char { |
||||
display: inline-block; |
||||
transform: translateY(8px); |
||||
width: 20px; |
||||
height: 20px; |
||||
border-radius: 3px; |
||||
animation: bounce 0.5s infinite alternate; |
||||
} |
||||
.char:nth-child(2) { |
||||
animation-delay: 0.1s; |
||||
} |
||||
|
||||
.char:nth-child(3) { |
||||
animation-delay: 0.2s; |
||||
} |
||||
|
||||
.char:nth-child(4) { |
||||
animation-delay: 0.3s; |
||||
} |
||||
|
||||
.char:nth-child(5) { |
||||
animation-delay: 0.4s; |
||||
} |
||||
|
||||
.char:nth-child(6) { |
||||
animation-delay: 0.5s; |
||||
} |
||||
|
||||
.char:nth-child(7) { |
||||
animation-delay: 0.6s; |
||||
} |
||||
|
||||
.char:nth-child(8) { |
||||
animation-delay: 0.7s; |
||||
} |
||||
|
||||
@keyframes bounce { |
||||
0% { |
||||
transform: translateY(8px); |
||||
border-radius: 5px; |
||||
} |
||||
100% { |
||||
transform: translateY(-8px); |
||||
border-radius: 3px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
.template { |
||||
padding: 20px; |
||||
text-align: center; |
||||
|
||||
.loader { |
||||
margin: 25px; |
||||
} |
||||
} |
||||
|
||||
</style> |
After Width: | Height: | Size: 1019 B |
@ -0,0 +1,78 @@
|
||||
{ |
||||
"id": "jp-virtual-list", |
||||
"displayName": "虚拟列表 长列表优化 大数据列表高度不固定时展示,支持下拉刷新和回到顶部", |
||||
"version": "2.0.1", |
||||
"description": "当需要渲染大量的列表数据时,如果一次性将所有的数据渲染到DOM会导致页面崩溃。为了解决这个问题,可以使用虚拟滚动技术,只渲染可见区域内的数据,将未展示的数据暂不渲染", |
||||
"keywords": [ |
||||
"虚拟滚动列表", |
||||
"百万级数据展示", |
||||
"只渲染可见区域内的数据", |
||||
"虚拟列表", |
||||
"长列表" |
||||
], |
||||
"engines": { |
||||
"HBuilderX": "^3.1.0" |
||||
}, |
||||
"dcloudext": { |
||||
"sale": { |
||||
"regular": { |
||||
"price": "0.00" |
||||
}, |
||||
"sourcecode": { |
||||
"price": "0.00" |
||||
} |
||||
}, |
||||
"contact": { |
||||
"qq": "" |
||||
}, |
||||
"declaration": { |
||||
"ads": "无", |
||||
"data": "插件不采集任何数据", |
||||
"permissions": "无" |
||||
}, |
||||
"type": "component-vue" |
||||
}, |
||||
"uni_modules": { |
||||
"encrypt": [], |
||||
"platforms": { |
||||
"cloud": { |
||||
"tcb": "y", |
||||
"aliyun": "y" |
||||
}, |
||||
"client": { |
||||
"App": { |
||||
"app-vue": "y", |
||||
"app-nvue": "y" |
||||
}, |
||||
"H5-mobile": { |
||||
"Safari": "y", |
||||
"Android Browser": "y", |
||||
"微信浏览器(Android)": "y", |
||||
"QQ浏览器(Android)": "y" |
||||
}, |
||||
"H5-pc": { |
||||
"Chrome": "y", |
||||
"IE": "y", |
||||
"Edge": "y", |
||||
"Firefox": "y", |
||||
"Safari": "y" |
||||
}, |
||||
"小程序": { |
||||
"微信": "y", |
||||
"阿里": "y", |
||||
"百度": "y", |
||||
"字节跳动": "y", |
||||
"QQ": "y" |
||||
}, |
||||
"快应用": { |
||||
"华为": "y", |
||||
"联盟": "y" |
||||
}, |
||||
"Vue": { |
||||
"vue2": "y", |
||||
"vue3": "y" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,94 @@
|
||||
<template> |
||||
<view class="h100"> |
||||
<view style="line-height: 40px;display: flex;justify-content: space-between;"> |
||||
<view @click="toAssign">滚动到:Item999</view> |
||||
<view @click="toAssign1">滚动到:Item100</view> |
||||
<view @click="toAssign2">滚动到:Item0</view> |
||||
</view> |
||||
<jp-virtual-list code="id" class="h100" :refresher="true" isBackTop @scrolltoupper="scrolltoupper" @refresher="refresher" :data="listx" ref="search"> |
||||
<template v-slot="{ list }"> |
||||
<view v-for="item in list" :key="item.id" :id="item.id" :ref="item.id"> |
||||
<view class="list_item" :style="'height:' + item.height +'rpx'"> |
||||
{{item.id}}随机高度:{{item.height}} |
||||
</view> |
||||
</view> |
||||
</template> |
||||
</jp-virtual-list> |
||||
</view> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
listx: [], |
||||
key: '' |
||||
} |
||||
}, |
||||
mounted() { |
||||
const data = [] |
||||
for (let i = 0; i < 10000; i++) { |
||||
data.push({ |
||||
name: `Item ${i}`, |
||||
index: i, |
||||
id: `Item${i}`, |
||||
height: (Math.floor(Math.random() * 100)+100) |
||||
}) |
||||
} |
||||
this.listx = data |
||||
}, |
||||
methods: { |
||||
toAssign(){ |
||||
this.$refs.search.toAssign('Item999',this) |
||||
}, |
||||
toAssign1(){ |
||||
this.$refs.search.toAssign('Item100',this) |
||||
}, |
||||
toAssign2(){ |
||||
this.$refs.search.toAssign('Item0',this) |
||||
}, |
||||
scrolltoupper(){ |
||||
const data = [] |
||||
for (let i = 0; i < 30; i++) { |
||||
data.push({ |
||||
name: `Itemc ${i}`, |
||||
index: i, |
||||
id: `Itemc${i}${(Math.floor(Math.random() * 100)+100)}`, |
||||
height: (Math.floor(Math.random() * 100)+100) |
||||
}) |
||||
} |
||||
setTimeout(()=>{ |
||||
// 告诉组件我需要添加该数据 |
||||
this.$refs.search.addPage(data) |
||||
},1000) |
||||
}, |
||||
refresher(){ |
||||
const data = [] |
||||
for (let i = 0; i < 10000; i++) { |
||||
data.push({ |
||||
name: `Item ${i}`, |
||||
index: i, |
||||
id: `Item${i}`, |
||||
height: (Math.floor(Math.random() * 100)+100) |
||||
}) |
||||
} |
||||
setTimeout(()=>{ |
||||
this.listx = data |
||||
// 告诉组件刷新成功了 |
||||
this.$refs.search.refreshers() |
||||
},2000) |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
.h100{ |
||||
height: calc(100vh - 80px); |
||||
} |
||||
.list_item { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
border: 1px solid #f5f5f5; |
||||
} |
||||
</style> |
@ -0,0 +1,142 @@
|
||||
## 虚拟列表-解决大数据列表操作问题 |
||||
> **组件名:jp-virtual-list** |
||||
|
||||
### 安装方式 |
||||
|
||||
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 |
||||
|
||||
##有项目需要开发的请联系 QQ:371524845 |
||||
###开发不易,如果帮助到你的,请支持 有问题请留言,作者会积极更新 |
||||
|
||||
### 基本用法 |
||||
|
||||
```html |
||||
<template> |
||||
<view class="h100"> |
||||
<view style="line-height: 40px;display: flex;justify-content: space-between;"> |
||||
<view @click="toAssign">滚动到:Item999</view> |
||||
<view @click="toAssign1">滚动到:Item100</view> |
||||
<view @click="toAssign2">滚动到:Item0</view> |
||||
</view> |
||||
<jp-virtual-list code="id" class="h100" :refresher="true" isBackTop @scrolltoupper="scrolltoupper" @refresher="refresher" :data="listx" ref="search"> |
||||
<template v-slot="{ list }"> |
||||
<view v-for="item in list" :key="item.id" :id="item.id" :ref="item.id"> |
||||
<view class="list_item" :style="'height:' + item.height +'rpx'"> |
||||
{{item.id}}随机高度:{{item.height}} |
||||
</view> |
||||
</view> |
||||
</template> |
||||
</jp-virtual-list> |
||||
</view> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
listx: [], |
||||
key: '' |
||||
} |
||||
}, |
||||
mounted() { |
||||
const data = [] |
||||
for (let i = 0; i < 10000; i++) { |
||||
data.push({ |
||||
name: `Item ${i}`, |
||||
index: i, |
||||
id: `Item${i}`, |
||||
height: (Math.floor(Math.random() * 100)+100) |
||||
}) |
||||
} |
||||
this.listx = data |
||||
}, |
||||
methods: { |
||||
toAssign(){ |
||||
this.$refs.search.toAssign('Item999',this) |
||||
}, |
||||
toAssign1(){ |
||||
this.$refs.search.toAssign('Item100',this) |
||||
}, |
||||
toAssign2(){ |
||||
this.$refs.search.toAssign('Item0',this) |
||||
}, |
||||
scrolltoupper(){ |
||||
const data = [] |
||||
for (let i = 0; i < 30; i++) { |
||||
data.push({ |
||||
name: `Itemc ${i}`, |
||||
index: i, |
||||
id: `Itemc${i}${(Math.floor(Math.random() * 100)+100)}`, |
||||
height: (Math.floor(Math.random() * 100)+100) |
||||
}) |
||||
} |
||||
setTimeout(()=>{ |
||||
// 告诉组件我需要添加该数据 |
||||
this.$refs.search.addPage(data) |
||||
},1000) |
||||
}, |
||||
refresher(){ |
||||
const data = [] |
||||
for (let i = 0; i < 10000; i++) { |
||||
data.push({ |
||||
name: `Item ${i}`, |
||||
index: i, |
||||
id: `Item${i}`, |
||||
height: (Math.floor(Math.random() * 100)+100) |
||||
}) |
||||
} |
||||
setTimeout(()=>{ |
||||
this.listx = data |
||||
// 告诉组件刷新成功了 |
||||
this.$refs.search.refreshers() |
||||
},2000) |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
.h100{ |
||||
height: calc(100vh - 80px); |
||||
} |
||||
.list_item { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
border: 1px solid #f5f5f5; |
||||
} |
||||
</style> |
||||
``` |
||||
|
||||
##### |
||||
|
||||
## API |
||||
|
||||
### Props |
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 | |
||||
| | | | | |
||||
| direction| String | 'bottom' | 数据加载方向(bottom/top) | |
||||
| lazy | Boolean|false | 【2.0以上版本已弃用】 是否懒加载,两种模式一种懒加载(数据量在5000以下),虚拟dom(大数据模式) | |
||||
| pageSizes| Number | 20 | 加载分页数 | |
||||
| data | Array | [] | 数据列表 | |
||||
| refresher| Boolean |false| 是否拥有下拉刷新 | |
||||
| code| String |'id'| 到达指定元素(需要设置元素的id和ref为当前值,参考基本用法) | |
||||
| isBackTop | Boolean | false | 是否拥有回到顶部(当前只用 direction=='bottom' 时启用) | |
||||
##### * 注意jp-virtual-list需要一个高度 |
||||
|
||||
### 回调 |
||||
| 事件名 | 类型 | 回调参数 | 说明 | |
||||
| | | | | |
||||
| @operate | function |无 | 操作数据成功之后的回调 | |
||||
| @refresher | function |无 | 下拉刷新的回调 | |
||||
|
||||
### 事件 |
||||
| 事件名 | 说明 | |
||||
| | | |
||||
| refreshers | 下拉刷新成功之后调用,用来关闭刷新动画 | |
||||
| toAssign| 到达指定元素(需要设置到达元素的id和ref值为指定的数据,需要和code统一) | |
||||
| addPage| 向数组最后添加数据 | |
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in new issue