perf: virtualize management lists and make drag sorting opt-in

This commit is contained in:
jxxghp
2026-05-09 16:07:28 +08:00
parent a475085d7b
commit 96d655155a
14 changed files with 481 additions and 101 deletions

View File

@@ -4,6 +4,8 @@ const props = withDefaults(
items: any[]
minItemWidth?: number
itemAspectRatio?: number
estimatedItemHeight?: number
scrollToIndex?: number
gap?: number
overscanRows?: number
getItemKey?: (item: any, index: number) => string | number
@@ -11,6 +13,8 @@ const props = withDefaults(
{
minItemWidth: 144,
itemAspectRatio: 1.5,
estimatedItemHeight: undefined,
scrollToIndex: undefined,
gap: 16,
overscanRows: 4,
getItemKey: undefined,
@@ -27,7 +31,8 @@ const endIndex = ref(0)
let resizeObserver: ResizeObserver | null = null
let animationFrameId: number | null = null
const rowStep = computed(() => itemHeight.value + props.gap)
const effectiveItemHeight = computed(() => props.estimatedItemHeight ?? itemHeight.value)
const rowStep = computed(() => effectiveItemHeight.value + props.gap)
const totalRows = computed(() => Math.ceil(props.items.length / columnCount.value))
const visibleItems = computed(() => props.items.slice(startIndex.value, endIndex.value))
@@ -104,7 +109,7 @@ function syncVisibleRange() {
const columns = Math.max(1, Math.floor((containerWidth + props.gap) / (props.minItemWidth + props.gap)))
columnCount.value = columns
itemWidth.value = (containerWidth - props.gap * (columns - 1)) / columns
itemHeight.value = itemWidth.value * props.itemAspectRatio
itemHeight.value = props.estimatedItemHeight ?? itemWidth.value * props.itemAspectRatio
const rowHeight = rowStep.value || 1
const containerTop = window.scrollY + container.getBoundingClientRect().top
@@ -129,6 +134,30 @@ function queueSyncVisibleRange() {
})
}
function scrollToItemIndex(index: number) {
if (typeof window === 'undefined') {
return
}
const container = containerRef.value
if (!container || props.items.length === 0 || index < 0) {
return
}
syncVisibleRange()
const safeIndex = Math.min(index, props.items.length - 1)
const targetRow = Math.floor(safeIndex / columnCount.value)
const targetTop = window.scrollY + container.getBoundingClientRect().top + targetRow * rowStep.value
window.scrollTo({
top: Math.max(targetTop - props.gap, 0),
behavior: 'auto',
})
queueSyncVisibleRange()
}
onMounted(() => {
queueSyncVisibleRange()
window.addEventListener('scroll', queueSyncVisibleRange, { passive: true })
@@ -157,7 +186,13 @@ onUnmounted(() => {
})
watch(
() => props.items.length,
[
() => props.items.length,
() => props.minItemWidth,
() => props.itemAspectRatio,
() => props.estimatedItemHeight,
() => props.gap,
],
() => {
nextTick(() => {
queueSyncVisibleRange()
@@ -165,6 +200,20 @@ watch(
},
{ immediate: true },
)
watch(
[() => props.scrollToIndex, () => props.items.length],
([scrollToIndex]) => {
if (scrollToIndex === undefined || scrollToIndex < 0) {
return
}
nextTick(() => {
scrollToItemIndex(scrollToIndex)
})
},
{ immediate: true },
)
</script>
<template>