基于vue3.0和element-plus的组件库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

158 lines
3.8 KiB

/**
* useVirtualRows
*
* Custom virtualizer using prefix-sum offsets + binary search.
* Inspired by pretext-table approach - lightweight, works perfectly
* with pre-computed row heights from pretext.
*/
import { ref, computed, watch, type Ref } from "vue";
const DEFAULT_OVERSCAN = 5;
export interface VirtualRow {
index: number;
offsetY: number;
height: number;
}
export interface VirtualRange {
startIndex: number;
endIndex: number;
offsetY: number;
}
export interface UseVirtualRowsOptions {
overscan?: number;
}
/**
* Build a prefix-sum array from row heights for O(1) offset lookups.
*/
export function buildOffsets(heights: number[]): number[] {
const offsets = new Array(heights.length + 1);
offsets[0] = 0;
for (let i = 0; i < heights.length; i++) {
offsets[i + 1] = offsets[i] + heights[i];
}
return offsets;
}
/**
* Binary search: find the first row index where bottom edge >= scrollTop.
* Returns index in range [0, heights.length - 1].
*/
function findStartIndex(offsets: number[], scrollTop: number): number {
let lo = 0;
let hi = offsets.length - 2; // last valid row index
while (lo < hi) {
const mid = (lo + hi) >>> 1;
if (offsets[mid + 1] <= scrollTop) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
export function useVirtualRows(
rowHeights: Ref<number[]>,
viewportHeight: Ref<number>,
options?: UseVirtualRowsOptions
) {
const overscan = options?.overscan ?? DEFAULT_OVERSCAN;
const scrollTop = ref(0);
// Build prefix-sum offsets whenever rowHeights changes
const offsets = computed(() => buildOffsets(rowHeights.value));
// Total scrollable height
const totalHeight = computed(() => {
const last = offsets.value[offsets.value.length - 1];
return last ?? 0;
});
// Compute visible range
const range = computed<VirtualRange>(() => {
if (rowHeights.value.length === 0) {
return { startIndex: 0, endIndex: 0, offsetY: 0 };
}
const st = scrollTop.value;
const vp = viewportHeight.value;
const rawStart = findStartIndex(offsets.value, st);
const startIndex = Math.max(0, rawStart - overscan);
// Find end index: first row whose top is past scrollTop + viewportHeight
let endIndex = rawStart;
while (
endIndex < rowHeights.value.length &&
offsets.value[endIndex] < st + vp
) {
endIndex++;
}
endIndex = Math.min(rowHeights.value.length, endIndex + overscan);
return {
startIndex,
endIndex,
offsetY: offsets.value[startIndex],
};
});
// Get visible rows with their positions
const visibleRows = computed<VirtualRow[]>(() => {
const { startIndex, endIndex } = range.value;
const rows: VirtualRow[] = [];
// Safety check: ensure indices are valid
if (startIndex < 0 || endIndex < startIndex) {
return rows;
}
for (let i = startIndex; i < endIndex; i++) {
const offsetY = offsets.value[i];
const height = rowHeights.value[i];
if (offsetY === undefined || height === undefined) {
continue; // Skip invalid entries
}
rows.push({
index: i,
offsetY,
height,
});
}
return rows;
});
// Scroll handler to be attached to the scroll container
const onScroll = (newScrollTop: number) => {
scrollTop.value = newScrollTop;
};
// Scroll to a specific row index
const scrollToIndex = (index: number) => {
if (index < 0 || index >= offsets.value.length) return;
scrollTop.value = offsets.value[index];
};
// Scroll to a specific position
const scrollTo = (position: number) => {
scrollTop.value = Math.max(0, position);
};
return {
scrollTop,
totalHeight,
range,
visibleRows,
onScroll,
scrollToIndex,
scrollTo,
offsets,
};
}