52 lines
1.6 KiB
Vue
52 lines
1.6 KiB
Vue
<script setup lang="ts">
|
|
import { useVirtualizer } from '@tanstack/vue-virtual';
|
|
|
|
const props = defineProps<{
|
|
items: readonly any[] | any[];
|
|
keyField?: string;
|
|
scrollElement: HTMLElement | null;
|
|
itemSize: number;
|
|
overscan: number;
|
|
prerender?: number;
|
|
}>();
|
|
|
|
const rowVirtualizer = useVirtualizer(computed(() => ({
|
|
count: props.items.length,
|
|
getScrollElement: () => props.scrollElement,
|
|
estimateSize: () => props.itemSize,
|
|
overscan: props.overscan,
|
|
getItemKey: (index: number) => props.keyField ? props.items[index]?.[props.keyField] || index : index,
|
|
initialRect: {
|
|
width: 0,
|
|
height: props.prerender ? props.itemSize * props.prerender : 0
|
|
},
|
|
})));
|
|
|
|
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems());
|
|
const totalSize = computed(() => rowVirtualizer.value.getTotalSize());
|
|
|
|
defineExpose({
|
|
scrollToIndex: (index: number, options?: { align?: 'start' | 'center' | 'end' }) => {
|
|
rowVirtualizer.value.scrollToIndex(index, options);
|
|
}
|
|
})
|
|
|
|
watch(() => props.items, () => {
|
|
rowVirtualizer.value.measure();
|
|
}, { deep: false });
|
|
</script>
|
|
|
|
<template>
|
|
<div :style="{ height: `${totalSize}px`, width: '100%', position: 'relative' }" v-bind="$attrs">
|
|
<div v-for="virtualRow in virtualRows" :key="(virtualRow.key as any | number)" :style="{
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
height: `${virtualRow.size}`,
|
|
transform: `translateY(${virtualRow.start}px)`,
|
|
}">
|
|
<slot :item="props.items[virtualRow.index]" :index="virtualRow.index" />
|
|
</div>
|
|
</div>
|
|
</template> |