Preserve scroll position perfectly

This commit is contained in:
Alex Gleason 2022-06-01 18:47:07 -05:00
parent 28bd9b0f4b
commit 3d605913e8
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7

View file

@ -12,6 +12,11 @@ type Context = {
listClassName?: string, listClassName?: string,
} }
type SavedScrollPosition = {
index: number,
offset: number,
}
// NOTE: It's crucial to space lists with **padding** instead of margin! // NOTE: It's crucial to space lists with **padding** instead of margin!
// Pass an `itemClassName` like `pb-3`, NOT a `space-y-3` className // Pass an `itemClassName` like `pb-3`, NOT a `space-y-3` className
// https://virtuoso.dev/troubleshooting#list-does-not-scroll-to-the-bottom--items-jump-around // https://virtuoso.dev/troubleshooting#list-does-not-scroll-to-the-bottom--items-jump-around
@ -72,10 +77,11 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
const settings = useSettings(); const settings = useSettings();
const autoloadMore = settings.get('autoloadMore'); const autoloadMore = settings.get('autoloadMore');
// Preserve scroll index // Preserve scroll position
const scrollIndexKey = `soapbox:scrollIndex:${location.pathname}`; const scrollDataKey = `soapbox:scrollData:${location.pathname}`;
const scrollIndex = Number(sessionStorage.getItem(scrollIndexKey)); const scrollData: SavedScrollPosition | null = JSON.parse(sessionStorage.getItem(scrollDataKey)!);
const initialIndex = useRef(scrollIndex); const topIndex = useRef<number>(scrollData ? scrollData.index : 0);
const topOffset = useRef<number>(scrollData ? scrollData.offset : 0);
/** Normalized children */ /** Normalized children */
const elements = Array.from(children || []); const elements = Array.from(children || []);
@ -95,8 +101,22 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
data.push(<Spinner />); data.push(<Spinner />);
} }
const handleScroll = () => {
const node = document.querySelector(`[data-virtuoso-scroller] [data-item-index="${topIndex.current}"]`);
if (node) {
topOffset.current = node.getBoundingClientRect().top * -1;
}
};
useEffect(() => { useEffect(() => {
sessionStorage.removeItem(scrollIndexKey); document.addEventListener('scroll', handleScroll);
sessionStorage.removeItem(scrollDataKey);
return () => {
const data = { index: topIndex.current, offset: topOffset.current };
sessionStorage.setItem(scrollDataKey, JSON.stringify(data));
document.removeEventListener('scroll', handleScroll);
};
}, []); }, []);
/* Render an empty state instead of the scrollable list */ /* Render an empty state instead of the scrollable list */
@ -139,8 +159,8 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
} }
}; };
const handleRangeChanged = (range: ListRange) => { const handleRangeChange = (range: ListRange) => {
sessionStorage.setItem(scrollIndexKey, String(range.startIndex)); topIndex.current = range.startIndex;
}; };
/** Render the actual Virtuoso list */ /** Render the actual Virtuoso list */
@ -155,8 +175,8 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
endReached={handleEndReached} endReached={handleEndReached}
isScrolling={isScrolling => isScrolling && onScroll && onScroll()} isScrolling={isScrolling => isScrolling && onScroll && onScroll()}
itemContent={renderItem} itemContent={renderItem}
initialTopMostItemIndex={showLoading ? 0 : initialTopMostItemIndex || initialIndex.current} initialTopMostItemIndex={showLoading ? 0 : initialTopMostItemIndex || (scrollData ? { align: 'start', index: scrollData.index, offset: scrollData.offset } : 0)}
rangeChanged={handleRangeChanged} rangeChanged={handleRangeChange}
style={style} style={style}
context={{ context={{
listClassName: className, listClassName: className,