feat(createVirtualizer): add isVisible and proximity properties to VirtualItem, add filckering prevention check
This commit is contained in:
@@ -14,6 +14,10 @@ export interface VirtualItem {
|
||||
end: number;
|
||||
/** Unique key for the item (for Svelte's {#each} keying) */
|
||||
key: string | number;
|
||||
/** Whether the item is currently visible in the viewport */
|
||||
isVisible: boolean;
|
||||
/** Proximity of the item to the center of the viewport */
|
||||
proximity: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,6 +140,8 @@ export function createVirtualizer<T>(
|
||||
|
||||
let endIdx = startIdx;
|
||||
const viewportEnd = scrollOffset + containerHeight;
|
||||
const viewportCenter = scrollOffset + (containerHeight / 2);
|
||||
|
||||
while (endIdx < count && offsets[endIdx] < viewportEnd) {
|
||||
endIdx++;
|
||||
}
|
||||
@@ -144,13 +150,31 @@ export function createVirtualizer<T>(
|
||||
const end = Math.min(count, endIdx + overscan);
|
||||
|
||||
const result: VirtualItem[] = [];
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
const itemStart = offsets[i];
|
||||
const itemSize = measuredSizes[i] ?? options.estimateSize(i);
|
||||
const itemEnd = itemStart + itemSize;
|
||||
|
||||
// Visibility check: Does the item overlap the viewport?
|
||||
// const isVisible = itemStart < viewportEnd && itemEnd > scrollOffset;
|
||||
// Fully visible
|
||||
const isVisible = itemStart >= scrollOffset && itemEnd <= viewportEnd;
|
||||
|
||||
// Proximity calculation: 1.0 at center, 0.0 at edges
|
||||
const itemCenter = itemStart + (itemSize / 2);
|
||||
const distanceToCenter = Math.abs(viewportCenter - itemCenter);
|
||||
const maxDistance = containerHeight / 2;
|
||||
const proximity = Math.max(0, 1 - (distanceToCenter / maxDistance));
|
||||
|
||||
result.push({
|
||||
index: i,
|
||||
start: offsets[i],
|
||||
size: measuredSizes[i] ?? options.estimateSize(i),
|
||||
end: offsets[i] + (measuredSizes[i] ?? options.estimateSize(i)),
|
||||
start: itemStart,
|
||||
size: itemSize,
|
||||
end: itemEnd,
|
||||
key: options.getItemKey?.(i) ?? i,
|
||||
isVisible,
|
||||
proximity,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,23 +231,25 @@ export function createVirtualizer<T>(
|
||||
const index = parseInt(node.dataset.index || '', 10);
|
||||
const height = entry.borderBoxSize[0]?.blockSize ?? node.offsetHeight;
|
||||
|
||||
if (!isNaN(index) && measuredSizes[index] !== height) {
|
||||
// 1. Stuff the measurement into a temporary buffer
|
||||
if (!isNaN(index)) {
|
||||
const oldHeight = measuredSizes[index];
|
||||
// Only update if the height difference is significant (> 0.5px)
|
||||
// This prevents "jitter" from focus rings or sub-pixel border changes
|
||||
if (oldHeight === undefined || Math.abs(oldHeight - height) > 0.5) {
|
||||
// Stuff the measurement into a temporary buffer
|
||||
measurementBuffer[index] = height;
|
||||
|
||||
// 2. Schedule a single update for the next animation frame
|
||||
// Schedule a single update for the next animation frame
|
||||
if (frameId === null) {
|
||||
frameId = requestAnimationFrame(() => {
|
||||
// 3. Update the state once for all collected measurements
|
||||
// We use spread to trigger a single fine-grained update
|
||||
measuredSizes = { ...measuredSizes, ...measurementBuffer };
|
||||
|
||||
// 4. Reset the buffer
|
||||
// Reset the buffer
|
||||
measurementBuffer = {};
|
||||
frameId = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
resizeObserver.observe(node);
|
||||
|
||||
Reference in New Issue
Block a user