Skip to content

Conversation

@dpwls02142
Copy link

🎯 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: Scrolling up with dynamic heights stutters and jumps #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

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

🚀 Release Impact

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

@changeset-bot
Copy link

changeset-bot bot commented Dec 8, 2025

🦋 Changeset detected

Latest commit: d284d4f

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

@piecyk
Copy link
Collaborator

piecyk commented Dec 8, 2025

@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.

…educe stuttering, especially when scrolling lists with dynamically sized items
@dpwls02142 dpwls02142 force-pushed the feature/scroll-adjustment-optimization branch from f14d223 to d284d4f Compare December 10, 2025 01:18
@dpwls02142
Copy link
Author

@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 dpwls02142 changed the title feat(virtual-core): Optimize scroll adjustments with batching and add backward scroll option feat(virtual-core): add skipRemeasurementOnBackwardScroll option to reduce stuttering Dec 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants