Preserve scroll position perfectly
This commit is contained in:
parent
28bd9b0f4b
commit
3d605913e8
1 changed files with 29 additions and 9 deletions
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue