Post on: 2024-12-13Last edited: 2026-6-12Words 641Read Time 2 min

type
Post
status
Published
date
Dec 13, 2024
slug
content-auto-follow
summary
Auto-scroll in an AI chat is not just scrollTop. It needs intent signals, a follow/free state machine, handling for programmatic scroll events, virtualization, and mobile edge cases.
tags
Tools
Development
Reflection
AI
category
Technology
icon
password
paired_with
15b1d487-a2a1-8037-b712-e65d420c21a1
lang
en-US
translation_locked
source_hash
5702b96809e03a4e24bfe5b036f3fdf1e567dbe2ba8d9355b3b6a63fe6adee76
💭
One of the easiest details to get wrong in an AI chat product is "automatically scroll to the bottom." The model is streaming tokens, new text keeps appearing, and ideally the window follows the latest output. But if the user scrolls up to reread something, the interface should politely stop following. The tiny question of whether the user still wants to follow looks like two lines of code, but it can cost months of small fixes. This post is a record of the versions I went through.

The Simplest Version

My first version was the same one most people write: use a threshold. If the user is within some number of pixels from the bottom, assume they want to keep following.
It feels smooth at first. But as soon as the user scrolls up to read a long answer, a strange thing happens. The model is still generating, so scrollHeight keeps growing. distance grows too, so shouldAutoScroll becomes false and the view seems to stop. Then if the user scrolls down close to the threshold again, the logic suddenly jumps to the bottom and throws away the part they were reading.

Version Two: Add A State Machine

Once I realized this was a state problem, I made the "following mode" explicit.
The key is to separate "the user moved the page" from "new content changed the page height." User movement changes the mode. New content only scrolls automatically while the mode is following.
There is one more detail: how do you tell the difference between a user scroll and a scroll caused by your own scrollTop = scrollHeight? Setting scrollTop also fires a scroll event. I usually handle that with a short flag:

Long Conversations And Performance

The problem is not only state. Once a conversation grows to thousands of rows, keeping every DOM node mounted makes scrolling itself slow. Then you need virtualization: render only the items near the viewport.
Virtualization makes "scroll to bottom" harder. scrollHeight is now a virtual height, not the real height of every rendered node. scrollTop = scrollHeight no longer always means "show the last message." You often need a dedicated scrollToIndex(lastIndex) instead.

Hidden Edge Cases

There are more traps than the first version suggests.
  • Window resizing. When the browser window changes size, clientHeight changes and the distance calculation needs to run again.
  • Font-size changes. If the user zooms the page, each item height changes. Virtual scrolling that assumes a fixed itemHeight can break.
  • Images loading late. If the model outputs an image, its height may be 0 before it loads and much larger after it loads, causing the scroll position to jump.
  • Keyboard navigation. Page Up should count as "the user wants to browse freely" and move the mode to free.
  • Touch scrolling. On mobile, wheel events are not enough. You need to listen to touch movement too.
None of these cases is especially hard alone. Together, they are enough to turn a small feature into a week of work.

Bottom Line

The main thing I learned is that features involving user intent should not try to guess the user's mind. They should record what the user actually did. "Smart following" is mostly a state machine over user scroll behavior: intentionally leaving, intentionally returning, and passive jumps caused by content changes. Machine learning is not the answer here. A clear state machine is.

References


Loading...
Learning Algorithms: Understand First, Code Second

Learning Algorithms: Understand First, Code Second

Using linked-list insertion and binary-tree traversal as examples, this post argues that algorithm learning works better when you draw the structure, simulate the steps, explain the invariant, and only then write code.


Avoid Overusing React useEffect

Avoid Overusing React useEffect

useEffect is useful for side effects outside rendering, but too many effects turn a component into a dependency maze. This post uses a concrete dashboard example to show when to extract custom hooks or move data fetching elsewhere.


Announcement
This site is still updating…