Cleanup "useReaderPreserveScrollPosition"

This commit is contained in:
schroda
2025-04-05 12:55:46 +02:00
parent da34c77f03
commit d89fbf9cf5

View File

@@ -21,84 +21,92 @@ export const useReaderPreserveScrollPosition = (
setPageToScrollToIndex: ReaderStatePages['setPageToScrollToIndex'], setPageToScrollToIndex: ReaderStatePages['setPageToScrollToIndex'],
pageScaleMode: ReaderPageScaleMode, pageScaleMode: ReaderPageScaleMode,
) => { ) => {
const scrollPosition = useRef({ const scrollPosition = useRef<{
left: number;
top: number;
visibleElement: HTMLElement | undefined;
visibleElementLeft: number;
visibleElementTop: number;
}>({
left: 0, left: 0,
top: 0, top: 0,
active: undefined as HTMLElement | undefined, visibleElement: undefined,
activeLeft: 0, visibleElementLeft: 0,
activeTop: 0, visibleElementTop: 0,
}); });
const readerNavBarWidthRef = useRef<number>(readerNavBarWidth); const readerNavBarWidthRef = useRef(readerNavBarWidth);
useEffect(() => { useEffect(() => {
const element = scrollElementRef.current; const scrollElement = scrollElementRef.current;
if (!element) { if (!scrollElement) {
return () => {}; return () => {};
} }
const onScroll = () => { const onScroll = () => {
const { active } = scrollPosition.current; const { visibleElement } = scrollPosition.current;
scrollPosition.current = { scrollPosition.current = {
...scrollPosition.current, ...scrollPosition.current,
left: element.scrollLeft, left: scrollElement.scrollLeft,
top: element.scrollTop, top: scrollElement.scrollTop,
activeLeft: active?.offsetLeft ?? 0, visibleElementLeft: visibleElement?.offsetLeft ?? 0,
activeTop: active?.offsetTop ?? 0, visibleElementTop: visibleElement?.offsetTop ?? 0,
}; };
}; };
element.addEventListener('scroll', onScroll); scrollElement.addEventListener('scroll', onScroll);
return () => element.removeEventListener('scroll', onScroll); return () => scrollElement.removeEventListener('scroll', onScroll);
}, []); }, []);
// on rendering previous chapter (infinite scroll in continuous reading modes) // on rendering previous chapter (infinite scroll in continuous reading modes)
const onDoPreserveScroll = useCallback(() => { const onDoPreserveScroll = useCallback(() => {
const scrollElement = scrollElementRef.current; const scrollElement = scrollElementRef.current;
const { left, top, active, activeLeft, activeTop } = scrollPosition.current; const { left, top, visibleElement, visibleElementLeft, visibleElementTop } = scrollPosition.current;
if (!scrollElement || !isContinuousReadingModeActive) { if (!scrollElement || !isContinuousReadingModeActive || !visibleElement) {
return; return;
} }
if (!active) return; const newLeft = left - visibleElementLeft + visibleElement.offsetLeft;
const newTop = top - visibleElementTop + visibleElement.offsetTop;
const newLeft = left - activeLeft + active.offsetLeft;
const newTop = top - activeTop + active.offsetTop;
scrollElement.scrollTo(newLeft, newTop); scrollElement.scrollTo(newLeft, newTop);
}, [scrollElementRef, isContinuousReadingModeActive]); }, [isContinuousReadingModeActive]);
useLayoutEffect(() => { useLayoutEffect(() => {
const element = scrollElementRef.current; const scrollElement = scrollElementRef.current;
if (!element) { if (!scrollElement) {
return () => {}; return () => {};
} }
const updateObservation = ( const updateObservation = (
nodes: NodeList, nodes: NodeList,
resizeAction: (n: Element) => void, resizeAction: (element: Element) => void,
intersectionAction: (n: Element) => void, intersectionAction: (element: Element) => void,
) => ) =>
Array.from(nodes) Array.from(nodes)
.filter((n) => n instanceof HTMLElement) .filter((node) => node instanceof HTMLElement)
.flatMap((n) => { .flatMap((element) => {
resizeAction(n); resizeAction(element);
return Array.from(n.querySelectorAll('img')); return Array.from(element.querySelectorAll('img'));
}) })
.forEach(intersectionAction); .forEach(intersectionAction);
const resizeObserver = new ResizeObserver(onDoPreserveScroll); const resizeObserver = new ResizeObserver(onDoPreserveScroll);
const intersectionObserver = new IntersectionObserver((entries) => { const intersectionObserver = new IntersectionObserver((entries) => {
// find the first visible image inside the viewport const firstVisibleElement = entries.filter((e) => e.isIntersecting).shift();
const first = entries.filter((e) => e.isIntersecting).shift();
if (!first || !(first.target instanceof HTMLElement)) return; if (!firstVisibleElement || !(firstVisibleElement.target instanceof HTMLElement)) {
return;
}
scrollPosition.current = { scrollPosition.current = {
...scrollPosition.current, ...scrollPosition.current,
active: first.target, visibleElement: firstVisibleElement.target,
activeLeft: first.target.offsetLeft, visibleElementLeft: firstVisibleElement.target.offsetLeft,
activeTop: first.target.offsetTop, visibleElementTop: firstVisibleElement.target.offsetTop,
}; };
}); });
const mutationObserver = new MutationObserver((entries) => { const mutationObserver = new MutationObserver((entries) => {
@@ -110,12 +118,13 @@ export const useReaderPreserveScrollPosition = (
); );
updateObservation( updateObservation(
entry.removedNodes, entry.removedNodes,
(n) => resizeObserver.unobserve(n), (element) => resizeObserver.unobserve(element),
(n) => intersectionObserver.unobserve(n), (element) => intersectionObserver.unobserve(element),
); );
} }
}); });
mutationObserver.observe(element, {
mutationObserver.observe(scrollElement, {
childList: true, childList: true,
}); });
@@ -124,7 +133,7 @@ export const useReaderPreserveScrollPosition = (
resizeObserver.disconnect(); resizeObserver.disconnect();
intersectionObserver.disconnect(); intersectionObserver.disconnect();
}; };
}, [scrollElementRef, onDoPreserveScroll]); }, [onDoPreserveScroll]);
const onAvailableReaderWidthChange = useCallback(() => { const onAvailableReaderWidthChange = useCallback(() => {
if (!isContinuousReadingModeActive) { if (!isContinuousReadingModeActive) {