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
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<T>(
// 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<T>(
let measurementBuffer: Record<number, number> = {};
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<T>(
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;
});