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;
|
end: number;
|
||||||
/** Unique key for the item (for Svelte's {#each} keying) */
|
/** Unique key for the item (for Svelte's {#each} keying) */
|
||||||
key: string | number;
|
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;
|
let endIdx = startIdx;
|
||||||
const viewportEnd = scrollOffset + containerHeight;
|
const viewportEnd = scrollOffset + containerHeight;
|
||||||
|
const viewportCenter = scrollOffset + (containerHeight / 2);
|
||||||
|
|
||||||
while (endIdx < count && offsets[endIdx] < viewportEnd) {
|
while (endIdx < count && offsets[endIdx] < viewportEnd) {
|
||||||
endIdx++;
|
endIdx++;
|
||||||
}
|
}
|
||||||
@@ -144,13 +150,31 @@ export function createVirtualizer<T>(
|
|||||||
const end = Math.min(count, endIdx + overscan);
|
const end = Math.min(count, endIdx + overscan);
|
||||||
|
|
||||||
const result: VirtualItem[] = [];
|
const result: VirtualItem[] = [];
|
||||||
|
|
||||||
for (let i = start; i < end; i++) {
|
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({
|
result.push({
|
||||||
index: i,
|
index: i,
|
||||||
start: offsets[i],
|
start: itemStart,
|
||||||
size: measuredSizes[i] ?? options.estimateSize(i),
|
size: itemSize,
|
||||||
end: offsets[i] + (measuredSizes[i] ?? options.estimateSize(i)),
|
end: itemEnd,
|
||||||
key: options.getItemKey?.(i) ?? i,
|
key: options.getItemKey?.(i) ?? i,
|
||||||
|
isVisible,
|
||||||
|
proximity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,23 +231,25 @@ export function createVirtualizer<T>(
|
|||||||
const index = parseInt(node.dataset.index || '', 10);
|
const index = parseInt(node.dataset.index || '', 10);
|
||||||
const height = entry.borderBoxSize[0]?.blockSize ?? node.offsetHeight;
|
const height = entry.borderBoxSize[0]?.blockSize ?? node.offsetHeight;
|
||||||
|
|
||||||
if (!isNaN(index) && measuredSizes[index] !== height) {
|
if (!isNaN(index)) {
|
||||||
// 1. Stuff the measurement into a temporary buffer
|
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;
|
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) {
|
if (frameId === null) {
|
||||||
frameId = requestAnimationFrame(() => {
|
frameId = requestAnimationFrame(() => {
|
||||||
// 3. Update the state once for all collected measurements
|
|
||||||
// We use spread to trigger a single fine-grained update
|
|
||||||
measuredSizes = { ...measuredSizes, ...measurementBuffer };
|
measuredSizes = { ...measuredSizes, ...measurementBuffer };
|
||||||
|
// Reset the buffer
|
||||||
// 4. Reset the buffer
|
|
||||||
measurementBuffer = {};
|
measurementBuffer = {};
|
||||||
frameId = null;
|
frameId = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeObserver.observe(node);
|
resizeObserver.observe(node);
|
||||||
|
|||||||
Reference in New Issue
Block a user