监听滚动条来做数据加载这个功能在移动端网站上比较常见,几乎所有"UI框架"都提供了这个功能,特别是vue实现的更加不能例外。
不过移动端有个非常大的弊端是不能通过监听滚动条来及时加载数据,必须等到滚动条完全结束才会执行回调onscroll
这个可能是考虑移动端上面性能原因,但是有时候的确会造成很大问题。。。
我们可以使用setInterval或者setTimeout来实现,而不是使用onscroll事件(更新可以监听touchmove),不过一定要注意不要产生多个定时器,必须在特定场景清除定时器。
setInterval或者setTimeout包含的代码会放在主程序代码之后再运行不会对主流程造成不良影响
- 因为VUE的指令是绑定在节点上面的,所有可以忽略监听window滚动的情况,下面的代码忽略window的滚动
- 考虑-----子元素高度没有滚动容器高度大的情景
- 考虑------滚动元素滚动快要到底部或者到底部的情景
- 其他情景都禁止加载数据
- 指令程序不控制数据加载状态
- 程序不控制数据加载过程,只负责控制知否调用数据加载
- 如果希望ios系统下滚动元素有弹性,可以给滚动容器加样式"-webkit-overflow-scrolling:touch;"
html模板:
<wv-group
title="无限滚动加载"
v-infinite-scroll="loadMore" //控制回调
infinite-scroll-disabled="loading" //控制是否停止加载程序
infinite-scroll-distance="50" //控制提前加载
>
<template>
<div class="page">
<div class="page-infinite-wrapper" ref="wrapper"
v-x-infinite-scroll="loadMore"
infinite-scroll-disabled="loaddisabled"
infinite-scroll-distance="50">
<wv-group
title="无限滚动加载"
>
<wv-cell title="条目" v-for="(item,index) in list" :key="item" :value="item"/>
</wv-group>
<p v-show="loading" class="loading-tips">
<wv-spinner type="snake" color="#444" :size="24"/>
</p>
</div>
</div>
</template>
<script>
export default {
data () {
return {
list: [],
loaddisabled:false,
disabledHandle:false,//可以加多一个属性来控制禁止
loading: false,
allLoaded: false,
wrapperHeight: 0
}
},
methods: {
loadMore () {
console.log('loading more')
this.loading = true;
this.loaddisabled=true;
setTimeout(() => {
let last = this.list.length?this.list[this.list.length - 1]:0;
for (let i = 1; i <= 5; i++) {
this.list.push(last + i)
}
this.$nextTick(() => {
this.loading = false;
this.loaddisabled=false;//有必要等数据加载完后在再继续加载数据
})
}, 2000)
}
}
}
</script>
<style lang="scss">
.loading-tips {
color: #222;
text-align: center;
}
html,body,#app,.page,.page-infinite-wrapper{
width:100%;
height:100%;
}
.page-infinite-wrapper{
overflow-Y:scroll;
-webkit-overflow-scrolling:touch;
}
</style>
核心代码:
通过监听调用指令的组件的vm来$watch "infinite-scroll-disabled"来控制程序的开启关闭
this.vm.$watch(disabledExpr, (value) => {
/*...............*/
//切换是否开启定时器
if (!this.disabled) {
clearTimeout(timer);
timer = setTimeout(() => {
loadMore.call(this, el)
}, 500)
} else {
clearTimeout(timer);
}
/*...............*/
})
在bind里面把使用当前指令的对象作用域赋值给el[CONTEXT],以便后续程序调用
el[CONTEXT] = {
el,
vm: vnode.context,
expression: binding.value
}
监听调用指令的节点是否加载完毕
el[CONTEXT].vm.$nextTick(function() {
doBindEvent.call(el[CONTEXT], el)
})
全部代码:
import scrollUtil from '../../utils/scroll'
const CONTEXT = '@@XInfiniteScroll'
const DISTANCE = 300;
let needLoadMore = false
let timer;
//获取滚动距离
function getScrollTop(element) {
return 'scrollTop' in element ? element.scrollTop : element.pageYOffset
}
function loadMore(el) {
//如果禁止加载,就不做任何事情
if (el.disabled) {
return;
}
console.log("moremore????", getScrollTop(el) + parseFloat(getComputedStyle(el, null).height), el.children[0].getBoundingClientRect().height-this.distance)
//如果之内容的高度小于滚动层的高度,就去拉取数据
if (el.getBoundingClientRect().height > el.children[0].getBoundingClientRect().height) {
needLoadMore = true;
console.log("less------------------------")
}
//监听容器滚动
else if (getScrollTop(el) + parseFloat(getComputedStyle(el, null).height) >= el.children[0].getBoundingClientRect().height - this.distance) {
needLoadMore = true;
console.log("moreload------------------------")
} else {
console.log("noloading------------------------")
needLoadMore = false;
}
if (needLoadMore && el[CONTEXT].expression) {
el[CONTEXT].expression();
};
//轮询
timer = setTimeout(() => {
loadMore.call(this, el)
}, 500);
}
const doBindEvent = function(el) {
const disabledExpr = this.el.getAttribute('infinite-scroll-disabled')
let distance = this.el.getAttribute('infinite-scroll-distance')
this.distance = Number(distance) || DISTANCE;
console.log("thisthisthisthis", this, this.distance)
//监听disabled
this.disabled=false;
if (disabledExpr) {
this.disabled=disabledExpr;
this.vm.$watch(disabledExpr, (value) => {
console.log("watchwatchwatchwatchwatchwatchwatchwatchwatchwatchwatchwatchwatchwatch======",)
this.disabled = value;
//切换是否开启定时器
if (!this.disabled) {
clearTimeout(timer);
timer = setTimeout(() => {
loadMore.call(this, el)
}, 500)
} else {
clearTimeout(timer);
}
})
}
loadMore.call(this, el)
}
export default {
bind(el, binding, vnode) {
if (!el[CONTEXT]) {
el[CONTEXT] = {
el,
vm: vnode.context,
expression: binding.value
}
}
el[CONTEXT].expression = binding.value;//绑定回调程序
//dom加载到浏览器才开始处理数据加载程序
el[CONTEXT].vm.$nextTick(function() {
doBindEvent.call(el[CONTEXT], el)
})
},
update(el) {
},
unbind(el) {
clearTimeout(timer); //组件销毁的时候清空定时器
}
}
全局注册代码指令的省略.....