vue3结合原生js长虚拟列表优化,并支持拖动到底部刷新数据,搬走可用

80 阅读1分钟
//根据自己的实际需求增加插槽
<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)//loading高度也要加上,否则拖到底会出现回弹一小段的情况,后续加入插槽的位置,只要在滚动范围内,都要加上
const listHeight = computed(() => data.length * itemHeight + loadingHeight.value )
const visibleData = ref([])//实际展示的数据
const offset = ref(0)
const start = ref(0)
const end = ref(0)
// 计算start和end的实际位置
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()
})