perf(chat): follow the live edge with a direct scroll write
Streaming follow scheduled a two-frame animation-frame chain per content growth and then went through the list's programmatic scrollToEnd, whose scroll bookkeeping schedules further frames. Together they roughly doubled frame production for the whole stream (16 -> 35 frames/s in a profiled streaming session), with the matching style recalc and layout cost per frame. In following-end mode, correct synchronously in the totalSize listener by writing scrollTop directly — the same write path a user gesture takes — and keep the animation-frame chain only for the anchored-turn glide. Profiled streaming frame production returns to the previous engine's range and peak heap drops from 164 MB to 90 MB.
This commit is contained in:
@@ -523,6 +523,28 @@ export const useChatTimelineScroll = ({
|
||||
const onTimelineDataChange = React.useCallback(() => {
|
||||
if (!isLiveFollowActive()) return;
|
||||
|
||||
// Following the end needs no animation frames: the totalSize listener
|
||||
// already fires after measurement, so correct synchronously the way
|
||||
// the previous scroll engine wrote scrollTop directly. Scheduling a
|
||||
// two-frame chain per streamed chunk kept a continuous rAF load (and
|
||||
// its per-frame style recalcs) running for the whole stream.
|
||||
if (modeRef.current === 'following-end') {
|
||||
const list = listRef.current;
|
||||
if (!list) return;
|
||||
if (!realContentOverflowsViewport(list)) return;
|
||||
// Write scrollTop directly instead of going through scrollToEnd:
|
||||
// the list's programmatic-scroll machinery schedules follow-up
|
||||
// animation frames per call, which doubles frame production for
|
||||
// the whole stream. A direct write is what a user gesture does,
|
||||
// and the list reconciles it through its normal onScroll path.
|
||||
const node = list.getScrollableNode();
|
||||
if (!node) return;
|
||||
// Overshoot so the browser clamps to the exact fractional maximum
|
||||
// (scrollHeight is integer-rounded).
|
||||
node.scrollTop = node.scrollHeight + 4096;
|
||||
return;
|
||||
}
|
||||
|
||||
const frames = dataChangeFramesRef.current;
|
||||
if (frames.first !== null) cancelAnimationFrame(frames.first);
|
||||
if (frames.second !== null) cancelAnimationFrame(frames.second);
|
||||
|
||||
Reference in New Issue
Block a user