// .vue
<div class="home">
<div ref="container" :style="containerStyle" @scroll="onScroll">
<div class="wrapper" :style="wrapperStyle">
<div
v-for="ele in list"
:key="ele.index"
:style="{
height: '72px',
border: '1px solid #e8e8e8',
marginBottom: '8px'
}"
>
Row: {{ ele.data }}
</div>
</div>
</div>
</div>
// /mixin/virtualList.js
export default {
data() {
return {
valueList: [],
start: 0,
end: 10,
itemHeight: 80, // 元素高度
overscan: 3 // 多渲染数
}
},
computed: {
list() { // 实际渲染数量
let valueList = this.valueList
let start = this.start
let end = this.end
return valueList.slice(start, end).map((item, index) => {
return {
data: item,
index: index + start
}
})
},
totalHeight() { // 元素总高度
let that = this
return that.list.length * that.itemHeight
},
offsetTop() { // 距顶部的距离
return this.start * this.itemHeight
},
containerStyle() { // 外层样式
return {
overflowY: "auto",
height: "100%"
}
},
wrapperStyle() { // 列表包裹样式
return {
width: "100%",
paddingTop: this.offsetTop + "px"
}
}
},
methods: {
getOffset(scrollTop) {
// 滚动到可见区域外的元素数量
return Math.floor(scrollTop / this.itemHeight) + 1
},
getViewCapacity(containerHeight) {
// 可见区域内能显示的数量
return Math.ceil(containerHeight / this.itemHeight)
},
calculateRange() {
// 计算实际渲染数据
let that = this
const element = that.$refs.container
if (element) {
const offset = that.getOffset(element.scrollTop)
const viewCapacity = that.getViewCapacity(element.clientHeight)
const overscan = that.overscan
const list = that.valueList
// 重新计算开始,滚动到到显示区域外的数量,减去预渲染数量,最小值为0
const from = offset - overscan
const start = from < 0 ? 0 : from
// 重新计算结束,最大值为实际数据列表的长度
const to = offset + viewCapacity + overscan
const end = to > list.length ? list.length : to
// 滚动到最后了,可以做增加原始数据的操作等
if (to === overscan + list.length + 1) {
const scrollBottom = that.scrollBottom
scrollBottom && scrollBottom()
}
that.start = start
that.end = end
}
},
onScroll(e) {
e.preventDefault()
this.calculateRange()
},
scrollTo(index) {
const element = this.$refs.container
if (element) {
element.scrollTop = index * this.itemHeight
this.calculateRange()
}
}
}
}