blog
blog copied to clipboard
Vue虚拟滚动实现原理
已知高度的处理方案
<template>
<div class="viewport" ref="viewport" @scroll="handleScroll">
<div class="scroll-bar" ref="scrollBar">
<!-- 可以滚动的容器 -->
</div>
<div class="scroll-list" :style="{transform: `translate3d(0, ${offset}px, 0)`}">
<!-- 可视区的列表 -->
<div v-for="item in visibleData" :key="item.id">
<slot :item="item"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
size: {
type: Number,
default: 0 // 每一项的高度
},
remain: {
type: Number,
default: 0 // 可视化区域显示多少个
},
items: {
type: Array,
default: () => [] // 列表
}
},
data() {
return {
start: 0,
offset: 0,
end: this.remain // 默认显示8个
};
},
computed: {
prevCount() {
return Math.min(this.start, this.remain)
},
nextCount () {
// 总个数不足就使用剩下的个数
return Math.min(this.end, this.items.length - this.end)
},
visibleData() {
let start = this.start - this.prevCount;
let end = this.end + this.nextCount;
return this.items.slice(start, end);
}
},
mounted() {
this.$refs.viewport.style.height = this.size * this.remain + "px";
this.$refs.scrollBar.style.height = this.items.length * this.size + "px";
},
methods: {
handleScroll() {
let scrollTop = this.$refs.viewport.scrollTop; // 滚动了多少距离
this.start = Math.floor(scrollTop / this.size); // 第几个开始,向下取整,因为还有即使还有一半没显示也要保留
this.end = this.start + this.remain;
// 去掉滚动完成的那几个
this.offset = this.start * this.size - this.size * this.prevCount; // 显示三屏,要减去预留渲染
}
}
};
</script>
<style>
.viewport {
overflow: auto;
position: relative;
}
.scroll-list {
position: absolute;
left: 0;
top: 0;
width: 100%;
transform: translate3d(0, 0, 0);
}
</style>
使用
<template>
<div class="hello">
<virtual-list :size="40" :remain="8" :items="items">
<template slot-scope="{item}">
<div class="item">
{{item.value}}
</div>
</template>
</virtual-list>
</div>
</template>