目录
- 1,效果展示
- 2,原理
- 2.1,滚动条的处理
- 2.2,展示内容处理
- 3,实现
vue-virtual-scroller
1,效果展示
1w 条数据无压力,看下初始渲染时间 Rendering 对比:
2,原理
目标:只加载在可视容器中的列表项。
实现:
2.1,滚动条的处理
- 为了保证滚动条正确显示和滑动,需要按照原本的数据量来计算滑动内容区域
wrapper
的真实高度, wrapper
外层的container
作为滑动容器,设置overflow:auto
+ 定高(比如500px)。
2.2,展示内容处理
最终目标:只展示可视区域内的 item,所以肯定会对源数据进行截取,需要计算出 startIndex
和 endIndex
。
- 为了保证只显示可视区域内的 item,可通过定位将每个 item 的初始位置都固定在第一行(
top: 0; left: 0
)。 - 在对每个 item 设置不同的偏移量
transform: translateY(index * itemHeight);
就可以正常展示了。 - 滑动时,需要根据滑动的距离
container.scrollTop
来重新计算startIndex
。再加上container.clientHeight
可以计算出endIndex
。 - 则可得出初始偏移量:
transform: translateY(startIndex* itemHeight);
3,实现
<!-- 父组件 -->
<template>
<div>
<RecycleScroller :items="list" :itemSize="54" v-slot="{ item }" class="scroller">
<div class="item-box">
<span>{{ item.id }}</span>
<span>{{ item.name }}</span>
</div>
</RecycleScroller>
</div>
</template>
<script>
import RecycleScroller from './components/RecycleScroller.vue'
const arr = []
for (let index = 0; index < 10000; index++) {
arr[index] = {
id: 'id' + index,
name: `name` + index
}
}
export default {
components: {
RecycleScroller
},
data() {
return {
list: arr
}
}
}
</script>
<style>
.scroller {
width: 200px;
height: 500px;
overflow: auto;
}
.item-box {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
}
</style>
<!-- RecycleScroller.vue -->
<template>
<div class="recycle-container" ref="container" @scroll="setPool">
<div class="recycle-wrapper" :style="{ height: totalSize }">
<div v-for="poolItem in pool" :key="poolItem.keyField" class="recycle-item" :style="{ transform: `translateY(${poolItem.position}px)` }">
<slot :item="poolItem.item"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
items: {
// 数据列表
type: Array,
default: () => []
},
itemSize: {
// 每条数据的高度
type: Number,
default: 50
},
keyField: {
// items 中的唯一标识作为 key
type: String,
default: 'id'
}
},
data() {
return {
pool: [] // 会被渲染的列表内容
}
},
computed: {
totalSize() {
return this.items.length * this.itemSize + 'px'
}
},
methods: {
setPool() {
const scrollTop = this.$refs.container.scrollTop
const clientHeight = this.$refs.container.clientHeight
let startIndex = Math.floor(scrollTop / this.itemSize) || 0
let endIndex = Math.ceil((scrollTop + clientHeight) / this.itemSize)
const startPosition = startIndex * this.itemSize
// 每次都重新计算 item 对应的位置。
this.pool = this.items.slice(startIndex, endIndex).map((item, index) => ({
item,
position: startPosition + this.itemSize * index
}))
}
},
mounted() {
this.setPool()
}
}
</script>
<style scoped>
.recycle-container {
overflow: auto;
}
.recycle-wrapper {
position: relative;
}
.recycle-item {
position: absolute;
width: 100%;
top: 0;
left: 0;
}
</style>
如果担心滑动太快导致的白屏问题(没有计算渲染出来),可以在前后各增加10条数据,一般就没有问题了。
以上。