Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/witty-bears-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@tanstack/virtual-core': minor
---

feat: add skipRemeasurementOnBackwardScroll option to reduce stuttering

Adds new option to skip re-measuring already-cached items during backward scrolling.
This prevents scroll adjustments that conflict with the user's scroll direction, reducing stuttering in dynamic content like social feeds or chat messages.

When enabled, cached measurements are reused during backward scroll while `isScrolling` is true. Layout settles correctly once scrolling stops.

**Changes:**
- Added `skipRemeasurementOnBackwardScroll` option (default: `false`)
- Skip re-measurement in `_measureElement` when scrolling backward with cached items
9 changes: 9 additions & 0 deletions docs/api/virtualizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,15 @@ This option enables wrapping ResizeObserver measurements in requestAnimationFram

It helps prevent the "ResizeObserver loop completed with undelivered notifications" error by ensuring that measurements align with the rendering cycle. This can improve performance and reduce UI jitter, especially when resizing elements dynamically. However, since ResizeObserver already runs asynchronously, adding requestAnimationFrame may introduce a slight delay in measurements, which could be noticeable in some cases. If resizing operations are lightweight and do not cause reflows, enabling this option may not provide significant benefits.

### `skipRemeasurementOnBackwardScroll`

```tsx
skipRemeasurementOnBackwardScroll: boolean
```
When enabled, prevents re-measuring items that have already been measured during backward scrolling.
This reduces stuttering caused by scroll position adjustments that conflict with the user's scroll direction.
It is recommended to use this property when scrolling in situations where item heights change dynamically.

## Virtualizer Instance

The following properties and methods are available on the virtualizer instance:
Expand Down
17 changes: 17 additions & 0 deletions packages/virtual-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ export interface VirtualizerOptions<
enabled?: boolean
isRtl?: boolean
useAnimationFrameWithResizeObserver?: boolean
skipRemeasurementOnBackwardScroll?: boolean
}

export class Virtualizer<
Expand Down Expand Up @@ -447,6 +448,7 @@ export class Virtualizer<
isRtl: false,
useScrollendEvent: false,
useAnimationFrameWithResizeObserver: false,
skipRemeasurementOnBackwardScroll: false,
...opts,
}
}
Expand Down Expand Up @@ -879,6 +881,21 @@ export class Virtualizer<
}

if (node.isConnected) {
// Check if we should skip remeasuring during backward scroll
if (
this.options.skipRemeasurementOnBackwardScroll &&
this.scrollDirection === 'backward' &&
this.isScrolling
) {
const isAlreadyMeasured = this.itemSizeCache.has(key)
if (isAlreadyMeasured) {
// Skip remeasuring to prevent stuttering during backward scroll
// Use cached measurement instead
return
}
}

// Measure and update size
this.resizeItem(index, this.options.measureElement(node, entry, this))
}
}
Expand Down