<template>
<div class="virtual-list" @scroll="handleScroll">
<div class="virtual-list-phantom" :style="{ height: listHeight + 'px' }"></div>
<div class="virtual-list-content" :style="{ transform: `translateY(${offset}px)` }">
<div v-for="(item, index) in visibleData" :key="index" class="virtual-list-item">
<slot name="listItem" :item="item" :index="index"></slot>
</div>
<slot name="loading"></slot>
</div>
</div>
</template>
const { data, itemHeight, bufferSize } = defineProps({
data: Array,
itemHeight: Number,
bufferSize: Number,
})
const emtis = defineEmits(['scrollToBottom']);
const loadingHeight = ref(0)
const listHeight = computed(() => data.length * itemHeight + loadingHeight.value )
const visibleData = ref([])
const offset = ref(0)
const start = ref(0)
const end = ref(0)
const handleStartEnd = () => {
const virtualList = document.querySelector('.virtual-list')
const loadingDom = document.querySelector('.loading')
loadingHeight.value = loadingDom ? loadingDom.clientHeight : 0
const scrollTop = virtualList.scrollTop
start.value = Math.floor(scrollTop / itemHeight)
end.value = start.value + Math.ceil((virtualList.clientHeight+loadingHeight.value) / itemHeight)
}
const handleScroll = (e) => {
handleStartEnd()
updateVisibleData()
const target = e.target;
if (target.scrollTop + target.clientHeight >= target.scrollHeight) {
emtis('scrollToBottom')
}
}
const updateVisibleData = () => {
const startNum = Math.max(start.value - bufferSize, 0)
const endNum = Math.min(end.value + bufferSize, data.length)
visibleData.value = data.slice(startNum, endNum)
offset.value = startNum * itemHeight
}
onMounted(() => {
handleStartEnd()
updateVisibleData()
})