Skip to content

Commit cd846c6

Browse files
committed
smoother scroll
1 parent 6becc4f commit cd846c6

1 file changed

Lines changed: 38 additions & 21 deletions

File tree

apps/web/src/features/editor/components/TypingFeedback.tsx

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function TypingFeedback({
2424
syntaxThemeId,
2525
}: TypingFeedbackProps) {
2626
const activeCursorRef = useRef<HTMLSpanElement>(null);
27+
const scrollFrameRef = useRef<number | null>(null);
2728

2829
const lineCount = useMemo(() => {
2930
let count = 1;
@@ -41,30 +42,46 @@ export function TypingFeedback({
4142
const container = element.closest('.scrollbar-auto-hide') as HTMLElement;
4243

4344
if (container) {
44-
const rect = element.getBoundingClientRect();
45-
const containerRect = container.getBoundingClientRect();
46-
47-
// Only scroll down if cursor is below viewport, never scroll up
48-
const buffer = 100; // pixels from bottom before triggering scroll
49-
const isBelowView = rect.bottom > containerRect.bottom - buffer;
50-
51-
if (isBelowView) {
52-
// Calculate position to keep cursor visible with buffer from bottom
53-
const relativeTop = rect.top - containerRect.top + container.scrollTop;
54-
const elementHeight = rect.height;
55-
const containerHeight = container.clientHeight;
56-
57-
// Position cursor at 2/3 down the viewport (not centered, keeps more context above)
58-
const targetPosition = containerHeight * 0.66;
59-
const scrollTop = relativeTop - targetPosition + (elementHeight / 2);
60-
61-
// Only scroll if we need to go down
62-
if (scrollTop > container.scrollTop) {
63-
container.scrollTop = scrollTop;
64-
}
45+
// Cancel any pending scroll frame
46+
if (scrollFrameRef.current !== null) {
47+
cancelAnimationFrame(scrollFrameRef.current);
6548
}
49+
50+
// Schedule scroll update, but only one at a time
51+
scrollFrameRef.current = requestAnimationFrame(() => {
52+
scrollFrameRef.current = null;
53+
54+
const rect = element.getBoundingClientRect();
55+
const containerRect = container.getBoundingClientRect();
56+
57+
// Only scroll down if cursor is below viewport, never scroll up
58+
const buffer = 100; // pixels from bottom before triggering scroll
59+
const isBelowView = rect.bottom > containerRect.bottom - buffer;
60+
61+
if (isBelowView) {
62+
// Calculate position to keep cursor visible with buffer from bottom
63+
const relativeTop = rect.top - containerRect.top + container.scrollTop;
64+
const containerHeight = container.clientHeight;
65+
66+
// Position cursor at 2/3 down the viewport (not centered, keeps more context above)
67+
const targetPosition = containerHeight * 0.66;
68+
const scrollTop = relativeTop - targetPosition;
69+
70+
// Only scroll if we need to go down
71+
if (scrollTop > container.scrollTop) {
72+
container.scrollTop = scrollTop;
73+
}
74+
}
75+
});
6676
}
6777
}
78+
79+
// Cleanup on unmount
80+
return () => {
81+
if (scrollFrameRef.current !== null) {
82+
cancelAnimationFrame(scrollFrameRef.current);
83+
}
84+
};
6885
}, [typedLength]);
6986

7087
return (

0 commit comments

Comments
 (0)