1w 条数据无压力,看下初始渲染时间 Rendering 对比:
目标:只加载在可视容器中的列表项。
实现:
wrapper
的真实高度,wrapper
外层的 container
作为滑动容器,设置 overflow:auto
+ 定高(比如500px)。最终目标:只展示可视区域内的 item,所以肯定会对源数据进行截取,需要计算出 startIndex
和 endIndex
。
top: 0; left: 0
)。transform: translateY(index * itemHeight);
就可以正常展示了。container.scrollTop
来重新计算 startIndex
。再加上 container.clientHeight
可以计算出 endIndex
。transform: translateY(startIndex* itemHeight);
<!-- 父组件 -->
<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条数据,一般就没有问题了。
以上。