首先无限滚动是个什么东西?
一般的滚动列表,一直往下滚动,一直加载dom;无限滚动,一直滚动,会动态删除看不到的dom,并渲染可见的dom,dom数量一直是有个固定的值。
思路:
容器要有一个固定的高度,超出出现滚动条,后续我们会根据滚动到底部,或者头部,加载相应的数据
列表要有固定的宽度,这样才能精确的计算。
当容器滚动条到底部,加载数据,并把头部看不见的多余dom删除,数据加入缓存。
当容器滚动到头部,把数据从缓存取出,渲染到头部,并把尾部的多余的dom删除,数据加入缓存。
个人vue的实现:
<template>
<div class="wrap">
<div class="infinite" @scroll="scrollEvent" ref="scroll">
<p class="list" v-for="(item, index) in list" :key="index">{{ item }}</p>
</div>
<p class="tip" v-show="loading">loading...</p>
</div>
</template>
<script>
const MAX = 20
let tempTop = 0
let LOCK = false
let tempPrev = []
let tempNext = []
const getNums = (function () {
let count = 1
return function (step) {
if (!(typeof step === 'number')) {
return []
}
if (step <= 0) {
return []
}
let res = []
while (step > 0) {
res.push(count++)
step--
}
return res
}
})()
export default {
data () {
return {
list: getNums(10),
dom: null,
loading: false
}
},
methods: {
scrollEvent (e) {
let container = this.dom
let scrollHeight = container.scrollHeight
let scrollTop = container.scrollTop
let height = container.offsetHeight
// 头部
if (scrollTop === 0) {
container.scrollTop = 0
e.preventDefault()
if (LOCK) {
return false
}
LOCK = true
setTimeout(() => {
// 将缓存取出
this.loadingPrev()
// 缓存后10条
this.cacheNext()
LOCK = false
}, 200)
return false
}
// 防止二次底部触发
// if (scrollTop < tempTop) {
// return false
// }
// tempTop = scrollTop
// 触底
if((scrollTop + height) >= scrollHeight) {
container.scrollTop = scrollHeight - height
e.preventDefault()
// 加锁,防止二次底部触发
if (LOCK) {
return false
}
LOCK = true
this.loading = true
setTimeout(() => {
this.loadingData().then(() => {
this.loading = false
LOCK = false
// setTimeout(() => {
// LOCK = false
// }, 100)
// 检查是否需要缓存前面数据
this.cachePrev()
})
}, 1000)
}
},
loadingData () {
if (tempNext.length > 0) {
// 取缓存
this.loadingNext()
return Promise.resolve()
}
// 重新生成
return Promise.resolve(getNums(Math.round(Math.random() * 9 + 1))).then(res => {
this.list.push(...res)
})
},
cachePrev () {
if (this.list.length > MAX) {
// 将前面数据加入缓存
let count = this.list.length - MAX
let caches = this.list.splice(0, count)
tempPrev.push(caches)
let container = this.dom
let scrollHeight = container.scrollHeight
let height = container.offsetHeight
this.dom.scrollTop = (scrollHeight - height) - (40 * count) * 2
}
},
loadingPrev () {
let caches = tempPrev.pop()
if (caches) {
this.list = caches.concat(this.list)
}
},
cacheNext () {
if (this.list.length > MAX) {
// 将后面数据加入缓存
let count = this.list.length - MAX
let caches = this.list.splice(this.list.length - count, count)
tempNext.push(caches)
this.dom.scrollTop = 40 * count
}
},
loadingNext () {
let caches = tempNext.pop()
if (caches) {
this.list = this.list.concat(caches)
}
}
},
mounted () {
this.dom = this.$refs.scroll
}
}
</script>
<style scoped>
.wrap {
position: relative;
width: 320px;
height: 240px;
margin: 0 auto;
border: 1px solid #ccc;
}
.infinite {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-scrolling: touch;
-webkit-overflow-scrolling: touch;
}
.list {
width: 100%;
height: 40px;
line-height: 40px;
padding-left: 20px;
border-bottom: 1px solid #666;
}
.tip {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
padding: 5px 0;
background-color: rgba(255, 255, 255, 8);
color: #67C23A;
font-size: 12px;
text-align: center;
z-index: 5;
}
</style>