Skip to content

applyScrollAdjustment causes chat stream viewport to drift downward when a visible streaming item keeps growing #1218

Description

@kawayiYokami

Hi, we found what looks like a bad fit between applyScrollAdjustment and a streaming chat UI.

We’re using @tanstack/vue-virtual for a chat window with dynamically growing assistant messages.

The problem is:

  • if a streaming message is visible and keeps getting taller
  • the virtualizer keeps adjusting scrollTop
  • so the viewport slowly moves downward by itself

This is not "scroll to bottom".
It is more like "keep the same relative distance / keep the same visual anchor", but in a streaming chat this feels wrong.

What we expect in this scenario is:

  • if the user is not manually scrolling
  • and a visible streaming message grows in place
  • the viewport should stay where it is

In our case, not adjusting scroll is stable.
Adjusting scroll is what creates the bad experience, because the viewport gets dragged down continuously as the message grows.

What we observed

We traced the call chain and confirmed the movement comes from virtualizer size correction:

ResizeObserver -> resizeItem -> applyScrollAdjustment -> _scrollToOffset -> scrollToFn

So this is not our own "scroll to bottom" logic.
It is the internal scroll correction path.

Repro shape

The behavior is easiest to see when:

  • the viewport is currently looking at the streaming message itself
  • especially when there is little or no stable content above it in the viewport
  • the message keeps growing during streaming

Then scrollTop keeps increasing together with scrollHeight, and the distance from bottom stays roughly constant.

Example of what happens:

  • before growth: scrollTop = 900, distanceToBottom = 100
  • after growth: scrollTop = 1000, distanceToBottom = 100

From the user’s point of view, the page is moving by itself.

Why this feels wrong in chat streaming

I understand the goal of scroll correction is to prevent visible jumping when item sizes change.

But for a streaming chat message that is already on screen, this correction does the opposite: it creates visible motion that would not exist if scroll stayed untouched.

So for this case, "do not correct" is actually the more stable result.

Our workaround

We currently override scrollToFn and block only virtualizer scroll corrections during streaming.

In practice, we detect the internal correction by checking adjustments !== 0, and while chat streaming is active we skip that scroll request.

Very roughly:

function scrollToFn(offset, { adjustments, behavior }, instance) {
  if (chatStreamingActive && adjustments !== 0) {
    return
  }

  instance.scrollElement?.scrollTo({
    top: offset,
    behavior: behavior ?? 'auto',
  })
}

This has been working much better for us because:

  • manual scrolling still works
  • explicit "scroll to bottom" still works
  • but the viewport is no longer dragged downward by streaming height growth

Question

Is this expected behavior for applyScrollAdjustment?

And more importantly, would it make sense to support a first-class option for this use case, something like:

  • disable correction for visible item growth
  • disable correction during streaming / live updates
  • or make the correction strategy more controllable for chat UIs

Right now the internal correction seems correct for many list cases, but not for this streaming chat case.

If useful, I can also provide a minimal reproduction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions