Я создаю пользовательский интерфейс чата в Next.js (App Router) и пытаюсь сохранить положение прокрутки при загрузке старых сообщений вверху.
Я использую:
// Scroll detection for infinite loading at top
const fetchMoreOnTopReached = useCallback(
(containerRefElement?: HTMLDivElement | null) => {
if (!onScrollToTop || isFetchingMore || !containerRefElement) return;
// Check if we've reached the top threshold
if (
isScrollThresholdReached({
container: containerRefElement,
direction: ScrollDirection.TOP,
threshold: SCROLL_THRESHOLD_PX,
})
) {
// ✅ Take snapshot RIGHT HERE before triggering the fetch
previousScrollPositionFromBottomRef.current =
getScrollPositionFromBottom();
console.log("📸 Snapshot taken at threshold:", {
scrollHeight: containerRefElement.scrollHeight,
positionFromBottom: previousScrollPositionFromBottomRef.current,
});
onScrollToTop();
}
},
[onScrollToTop, isFetchingMore]
);
const getScrollPositionFromBottom = useCallback(() => {
const container = scrollContainerRef.current;
if (!container) return 0;
const { scrollTop, scrollHeight, clientHeight } = container;
return scrollHeight - scrollTop - clientHeight;
}, [scrollContainerRef]);
// Debug logging to inspect scroll behavior
useEffect(() => {
setInterval(() => {
const container = scrollContainerRef.current;
if (!container) return;
console.log("🔄 Scroll height:", container.scrollHeight);
console.log("🔄 Scroll position from bottom:", getScrollPositionFromBottom());
}, 1000);
}, [scrollContainerRef, getScrollPositionFromBottom]);
// Restore scroll position after messages are prepended
useLayoutEffect(() => {
const container = scrollContainerRef.current;
if (!container) return;
if (
isFetchingMore === false &&
previousScrollPositionFromBottomRef.current > 0
) {
const { scrollHeight, clientHeight } = container;
console.log("🔄 Restoring scroll height:", scrollHeight);
const newScrollTop =
scrollHeight -
previousScrollPositionFromBottomRef.current -
clientHeight;
console.log("🔄 Restoring scroll position:", {
newScrollTop,
positionFromBottom: previousScrollPositionFromBottomRef.current,
});
// Wait for the virtualizer to re-measure
requestAnimationFrame(() => {
requestAnimationFrame(() => {
virtualizer.scrollToOffset(newScrollTop, {
align: "start",
behavior: "auto",
});
});
});
// Reset ref
previousScrollPositionFromBottomRef.current = 0;
}
}, [isFetchingMore, getScrollPositionFromBottom, virtualizer]);
Это почти работает, но все равно приводит к небольшому скачку прокрутки в зависимости от того, насколько быстро виртуализатор пересчитывает высоту элемента.
Кто-нибудь нашел способ обработать "бесконечную прокрутку сверху" с помощью TanStack Virtual, сохраняя при этом положение прокрутки стабильно?
Любые примеры кода, стратегии синхронизации или вспомогательные утилиты/библиотеки, которые упростят эту задачу, были бы замечательными.
Я создаю [b]пользовательский интерфейс чата в Next.js (App Router)[/b] и пытаюсь сохранить [b]положение прокрутки[/b] при загрузке старых сообщений вверху. Я использую: [list] [*][code]@tanstack/react-virtual[/code] для виртуализации [*][code]useInfiniteQuery[/code] из @tanstack/react-query для загрузки сообщений [/list]
[b]Поведение, которое я хочу[/b] [list] [*]Когда пользователь прокручивает страницу вверх, я получаю предыдущую страницу сообщений [*]Новые сообщения добавляются в начале правильно [*]Но я хочу, чтобы положение прокрутки оставалось стабильным, чтобы пользователь не чувствовал «прыжка» [/list]
[b]Вот что я пробовал (TypeScript)[/b] [code]// Scroll detection for infinite loading at top const fetchMoreOnTopReached = useCallback( (containerRefElement?: HTMLDivElement | null) => { if (!onScrollToTop || isFetchingMore || !containerRefElement) return;
// Check if we've reached the top threshold if ( isScrollThresholdReached({ container: containerRefElement, direction: ScrollDirection.TOP, threshold: SCROLL_THRESHOLD_PX, }) ) { // ✅ Take snapshot RIGHT HERE before triggering the fetch previousScrollPositionFromBottomRef.current = getScrollPositionFromBottom();
console.log("📸 Snapshot taken at threshold:", { scrollHeight: containerRefElement.scrollHeight, positionFromBottom: previousScrollPositionFromBottomRef.current, });
// Wait for the virtualizer to re-measure requestAnimationFrame(() => { requestAnimationFrame(() => { virtualizer.scrollToOffset(newScrollTop, { align: "start", behavior: "auto", }); }); });
// Reset ref previousScrollPositionFromBottomRef.current = 0; } }, [isFetchingMore, getScrollPositionFromBottom, virtualizer]); [/code] Это почти работает, но все равно приводит к [b]небольшому скачку прокрутки[/b] в зависимости от того, насколько быстро виртуализатор пересчитывает высоту элемента.
Кто-нибудь нашел способ обработать "бесконечную прокрутку сверху" с помощью [b]TanStack Virtual[/b], сохраняя при этом положение прокрутки стабильно? Любые [b]примеры кода[/b], [b]стратегии синхронизации[/b] или [b]вспомогательные утилиты/библиотеки[/b], которые упростят эту задачу, были бы замечательными.