85 lines
2.5 KiB
Vue
85 lines
2.5 KiB
Vue
<script setup lang="ts">
|
|
import { useVirtualizer } from '@tanstack/vue-virtual';
|
|
|
|
const props = defineProps<{
|
|
items: any[];
|
|
keyField?: string;
|
|
scrollElement: HTMLElement | null;
|
|
minItemSize: number;
|
|
overscan: number;
|
|
prerender?: number;
|
|
}>();
|
|
|
|
const containerRef = ref<HTMLElement | null>(null);
|
|
const scrollMargin = ref(0);
|
|
|
|
const updateScrollMargin = () => {
|
|
if (containerRef.value && props.scrollElement) {
|
|
const containerRect = containerRef.value.getBoundingClientRect();
|
|
const scrollRect = props.scrollElement.getBoundingClientRect();
|
|
scrollMargin.value = containerRect.top - scrollRect.top + props.scrollElement.scrollTop;
|
|
}
|
|
};
|
|
|
|
const rowVirtualizer = useVirtualizer(computed(() => ({
|
|
count: props.items.length,
|
|
getScrollElement: () => props.scrollElement,
|
|
estimateSize: () => props.minItemSize,
|
|
overscan: props.overscan,
|
|
scrollMargin: scrollMargin.value,
|
|
getItemKey: (index: number) => props.keyField ? props.items[index]?.[props.keyField] || index : index,
|
|
})));
|
|
|
|
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems());
|
|
const totalSize = computed(() => rowVirtualizer.value.getTotalSize());
|
|
|
|
let resizeRafId: number | undefined = undefined;
|
|
const resizeObserver = new ResizeObserver(() => {
|
|
if (resizeRafId) return;
|
|
|
|
resizeRafId = requestAnimationFrame(() => {
|
|
resizeRafId = undefined;
|
|
rowVirtualizer.value.measure();
|
|
updateScrollMargin();
|
|
});
|
|
});
|
|
|
|
onMounted(() => {
|
|
resizeObserver.observe(containerRef.value!);
|
|
updateScrollMargin();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
resizeObserver.disconnect();
|
|
});
|
|
|
|
|
|
|
|
// Keep the measure function simple
|
|
const measureElement = (el: any) => {
|
|
if (el) {
|
|
rowVirtualizer.value.measureElement(el);
|
|
}
|
|
};
|
|
|
|
watch(() => props.items, () => {
|
|
rowVirtualizer.value.measure();
|
|
}, { deep: false });
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="containerRef" :style="{ height: `${totalSize}px`, width: '100%', position: 'relative' }">
|
|
<div v-for="virtualRow in virtualRows" :ref="(el) => measureElement(el as Element)"
|
|
:key="(virtualRow.key as any | number)" :data-index="virtualRow.index" :style="{
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
minHeight: `${virtualRow.size}px`,
|
|
transform: `translateY(${virtualRow.start - scrollMargin}px)`,
|
|
}">
|
|
<slot :item="props.items[virtualRow.index]" :index="virtualRow.index" />
|
|
</div>
|
|
</div>
|
|
</template>
|