feat(createVirtualizer): slidthly improve batching with version trigger

This commit is contained in:
Ilia Mashkov
2026-02-12 11:23:27 +03:00
parent 0e85851cfd
commit 8b02333c01

View File

@@ -120,9 +120,11 @@ export function createVirtualizer<T>(
// By wrapping the getter in $derived, we track everything inside it // By wrapping the getter in $derived, we track everything inside it
const options = $derived(optionsGetter()); const options = $derived(optionsGetter());
// This derivation now tracks: count, measuredSizes, AND the data array itself // This derivation now tracks: count, _version (for measuredSizes updates), AND the data array itself
const offsets = $derived.by(() => { const offsets = $derived.by(() => {
const count = options.count; const count = options.count;
// Implicit dependency on version signal
const v = _version;
const result = new Float64Array(count); const result = new Float64Array(count);
let accumulated = 0; let accumulated = 0;
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@@ -144,6 +146,8 @@ export function createVirtualizer<T>(
// We MUST read options.data here so Svelte knows to re-run // We MUST read options.data here so Svelte knows to re-run
// this derivation when the items array is replaced! // this derivation when the items array is replaced!
const { count, data } = options; const { count, data } = options;
// Implicit dependency
const v = _version;
if (count === 0 || containerHeight === 0 || !data) return []; if (count === 0 || containerHeight === 0 || !data) return [];
const overscan = options.overscan ?? 5; const overscan = options.overscan ?? 5;
@@ -318,6 +322,9 @@ export function createVirtualizer<T>(
let measurementBuffer: Record<number, number> = {}; let measurementBuffer: Record<number, number> = {};
let frameId: number | null = null; let frameId: number | null = null;
// Signal to trigger updates when mutating measuredSizes in place
let _version = $state(0);
/** /**
* Svelte action to measure individual item elements for dynamic height support. * Svelte action to measure individual item elements for dynamic height support.
* *
@@ -334,18 +341,25 @@ export function createVirtualizer<T>(
const height = entry.borderBoxSize[0]?.blockSize ?? node.offsetHeight; const height = entry.borderBoxSize[0]?.blockSize ?? node.offsetHeight;
if (!isNaN(index)) { if (!isNaN(index)) {
// Accessing the version ensures we have the latest state if needed,
// though here we just read the raw object.
const oldHeight = measuredSizes[index]; const oldHeight = measuredSizes[index];
// Only update if the height difference is significant (> 0.5px) // 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) { if (oldHeight === undefined || Math.abs(oldHeight - height) > 0.5) {
// Stuff the measurement into a temporary buffer // Stuff the measurement into a temporary buffer to batch updates
measurementBuffer[index] = height; measurementBuffer[index] = height;
// 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(() => {
measuredSizes = { ...measuredSizes, ...measurementBuffer }; // Mutation in place for performance
// Reset the buffer Object.assign(measuredSizes, measurementBuffer);
// Trigger reactivity
_version += 1;
// Reset buffer
measurementBuffer = {}; measurementBuffer = {};
frameId = null; frameId = null;
}); });