virtual icon indicating copy to clipboard operation
virtual copied to clipboard

feat(virtual-core): add skipRemeasurementOnBackwardScroll option to reduce stuttering

Open dpwls02142 opened this issue 1 month ago • 3 comments

🎯 Changes

  1. problem
  • During heavy scrolling with dynamic item sizes, frequent scroll adjustments can cause:
    • Layout thrashing due to multiple synchronous scroll position updates
    • Stuttering during backward scrolling because already-measured items are remeasured unnecessarily
  • Related issue: #659
  1. Solution
  • Batching Scroll Adjustments

    • Collect multiple adjustment deltas in pendingAdjustmentDeltas array
    • Use requestAnimationFrame to batch-apply all adjustments in a single frame
    • Reduces layout thrashing and improves scrolling performance
  • Backward Scroll Optimization

    • Add disableScrollAdjustmentOnBackwardScroll option
    • Skip remeasuring items that are already in cache during backward scroll
    • Prevents stuttering while maintaining measurement accuracy

✅ Checklist

  • [x] I have followed the steps in the Contributing guide.
  • [x] I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • [x] This change affects published code, and I have generated a changeset.
  • [x] This change is docs/CI/dev-only (no release).

dpwls02142 avatar Dec 08 '25 06:12 dpwls02142

🦋 Changeset detected

Latest commit: d284d4fb579348732e81d234af959e01e3e607e0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@tanstack/virtual-core Minor
@tanstack/angular-virtual Patch
@tanstack/lit-virtual Patch
@tanstack/react-virtual Patch
@tanstack/solid-virtual Patch
@tanstack/svelte-virtual Patch
@tanstack/vue-virtual Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

changeset-bot[bot] avatar Dec 08 '25 06:12 changeset-bot[bot]

@dpwls02142 Thanks for the PR, this is addressing a very real problem. I think there are actually two distinct changes here:

  1. Skipping re-measurement when scrolling backward
  2. Batching scroll adjustments via requestAnimationFrame

I’d strongly suggest splitting these into two separate PRs:

  1. Backward scroll optimization The option name reads like it disables scroll adjustment, but in reality it skips measurement under certain conditions. It might be worth either renaming it or being very explicit about this behavior in the docs. There’s also an open question around how we guarantee that the layout eventually settles correctly after scrolling stops (i.e. when isScrolling flips back to false).

  2. Scroll adjustment batching There is no scroll adjustment batching implemented as by designed as when scrolling quickly, you see more visible white space. Because the scroll adjustments are delayed to the next requestAnimationFrame, the content height changes while the scroll offset isn’t corrected immediately, so during fast scrolling you get gaps until the batch flushes. The faster you scroll, the more noticeable this becomes. This isn’t a trivial issue to solve, it’s the fundamental trade-off between less thrashing vs. more lag. With dynamic heights and fast scrolling, you really feel that lag.

piecyk avatar Dec 08 '25 08:12 piecyk

@dpwls02142 Thanks for the PR, this is addressing a very real problem. I think there are actually two distinct changes here:

  1. Skipping re-measurement when scrolling backward
  2. Batching scroll adjustments via requestAnimationFrame

I’d strongly suggest splitting these into two separate PRs:

  1. Backward scroll optimization The option name reads like it disables scroll adjustment, but in reality it skips measurement under certain conditions. It might be worth either renaming it or being very explicit about this behavior in the docs. There’s also an open question around how we guarantee that the layout eventually settles correctly after scrolling stops (i.e. when isScrolling flips back to false).
  2. Scroll adjustment batching There is no scroll adjustment batching implemented as by designed as when scrolling quickly, you see more visible white space. Because the scroll adjustments are delayed to the next requestAnimationFrame, the content height changes while the scroll offset isn’t corrected immediately, so during fast scrolling you get gaps until the batch flushes. The faster you scroll, the more noticeable this becomes. This isn’t a trivial issue to solve, it’s the fundamental trade-off between less thrashing vs. more lag. With dynamic heights and fast scrolling, you really feel that lag.

Hi @piecyk, thanks for the detailed feedback!

I agree with splitting this into two separate PRs. I'll focus on the backward scroll optimization and remove the batching approach.

Changes

  • Removed all batching code (pendingAdjustmentDeltas, scheduleAdjustmentBatch, flushAdjustments, adjustmentBatchTimer)
  • Renamed to skipRemeasurementOnBackwardScroll to clarify it's skipping measurement, not adjustment

About layout stability You're right to ask about this. My thinking is that by the time a user scrolls backward, they've already scrolled forward past those items, so the measurements were already captured during the initial forward scroll. We're just reusing those cached values during the backward scroll to avoid the stuttering.

If an item's size genuinely changed while off-screen, it would get picked up the next time it enters the viewport during normal (non-backward) scrolling.

Let me know if you see any issues with this approach!

dpwls02142 avatar Dec 10 '25 01:12 dpwls02142