From 8b02333c01ba6f936ab7085cb23fdd4507a5e552 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 12 Feb 2026 11:23:27 +0300 Subject: [PATCH] feat(createVirtualizer): slidthly improve batching with version trigger --- .../createVirtualizer.svelte.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/shared/lib/helpers/createVirtualizer/createVirtualizer.svelte.ts b/src/shared/lib/helpers/createVirtualizer/createVirtualizer.svelte.ts index 4b92eb3..9fabb92 100644 --- a/src/shared/lib/helpers/createVirtualizer/createVirtualizer.svelte.ts +++ b/src/shared/lib/helpers/createVirtualizer/createVirtualizer.svelte.ts @@ -120,9 +120,11 @@ export function createVirtualizer( // By wrapping the getter in $derived, we track everything inside it 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 count = options.count; + // Implicit dependency on version signal + const v = _version; const result = new Float64Array(count); let accumulated = 0; for (let i = 0; i < count; i++) { @@ -144,6 +146,8 @@ export function createVirtualizer( // We MUST read options.data here so Svelte knows to re-run // this derivation when the items array is replaced! const { count, data } = options; + // Implicit dependency + const v = _version; if (count === 0 || containerHeight === 0 || !data) return []; const overscan = options.overscan ?? 5; @@ -318,6 +322,9 @@ export function createVirtualizer( let measurementBuffer: Record = {}; 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. * @@ -334,18 +341,25 @@ export function createVirtualizer( const height = entry.borderBoxSize[0]?.blockSize ?? node.offsetHeight; 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]; + // 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 + // Stuff the measurement into a temporary buffer to batch updates measurementBuffer[index] = height; // Schedule a single update for the next animation frame if (frameId === null) { frameId = requestAnimationFrame(() => { - measuredSizes = { ...measuredSizes, ...measurementBuffer }; - // Reset the buffer + // Mutation in place for performance + Object.assign(measuredSizes, measurementBuffer); + + // Trigger reactivity + _version += 1; + + // Reset buffer measurementBuffer = {}; frameId = null; });