From a4e9e8f2644b55f93eca6114471df57f4e1426bc Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:51:16 +0100 Subject: [PATCH] Introduce "useIntersectionObserver" hook --- .../core/hooks/useIntersectionObserver.tsx | 51 ++++++++++++++++++ .../useReaderInfiniteScrollUpdateChapter.ts | 53 +++++++++---------- 2 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 src/modules/core/hooks/useIntersectionObserver.tsx diff --git a/src/modules/core/hooks/useIntersectionObserver.tsx b/src/modules/core/hooks/useIntersectionObserver.tsx new file mode 100644 index 00000000..ebf09f16 --- /dev/null +++ b/src/modules/core/hooks/useIntersectionObserver.tsx @@ -0,0 +1,51 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { RefObject, useLayoutEffect, useState } from 'react'; + +export const useIntersectionObserver = ( + ref: RefObject | HTMLElement | undefined | null, + callback: IntersectionObserverCallback, + { + ignoreInitialObserve = false, + root, + rootMargin, + threshold, + }: IntersectionObserverInit & { ignoreInitialObserve?: boolean } = {}, +): (() => void) => { + const [disconnect, setDisconnect] = useState<() => void>(() => {}); + + useLayoutEffect(() => { + const element = ref instanceof HTMLElement ? ref : ref?.current; + + if (!element) { + return () => {}; + } + + // gets immediately observed once on initial render + let isInitialObserve = true; + const intersectionObserver = new IntersectionObserver( + (...args) => { + if (ignoreInitialObserve && isInitialObserve) { + isInitialObserve = false; + return; + } + + callback(...args); + }, + { root, rootMargin, threshold }, + ); + intersectionObserver.observe(element); + + setDisconnect(() => () => intersectionObserver.disconnect()); + + return () => intersectionObserver.disconnect(); + }, [ref, callback, ignoreInitialObserve, root, rootMargin, threshold]); + + return disconnect; +}; diff --git a/src/modules/reader/hooks/useReaderInfiniteScrollUpdateChapter.ts b/src/modules/reader/hooks/useReaderInfiniteScrollUpdateChapter.ts index 7c79ee56..c977338b 100644 --- a/src/modules/reader/hooks/useReaderInfiniteScrollUpdateChapter.ts +++ b/src/modules/reader/hooks/useReaderInfiniteScrollUpdateChapter.ts @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useLayoutEffect } from 'react'; +import { useCallback, useMemo } from 'react'; import { ReaderControls } from '@/modules/reader/services/ReaderControls.ts'; import { ReadingDirection, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; import { @@ -15,6 +15,7 @@ import { } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; import { READING_DIRECTION_TO_THEME_DIRECTION } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts'; +import { useIntersectionObserver } from '@/modules/core/hooks/useIntersectionObserver.tsx'; interface ElementIntersection { start: boolean; @@ -188,17 +189,11 @@ export const useReaderInfiniteScrollUpdateChapter = ( openChapter: ReturnType, image: HTMLElement | null, ) => { - useLayoutEffect(() => { - if (!image || !isContinuousReadingMode(readingMode) || chapterToOpenId === undefined) { - return () => {}; - } - - // gets immediately observed once on initial render - let isInitialObserve = true; - const intersectionObserver = new IntersectionObserver( + useIntersectionObserver( + image, + useCallback( (entries) => { - if (isInitialObserve) { - isInitialObserve = false; + if (!isContinuousReadingMode(readingMode) || chapterToOpenId === undefined) { return; } @@ -235,23 +230,25 @@ export const useReaderInfiniteScrollUpdateChapter = ( openChapter(chapterId, false, false); } }, - { + [ + pageType, + chapterId, + chapterToOpenId, + isCurrentChapter, + isChapterToOpenVisible, + readingMode, + readingDirection, + openChapter, + ], + ), + useMemo( + () => ({ threshold: [OPEN_CHAPTER_INTERSECTION_RATIO], rootMargin: pageType === 'first' ? '0px 0px -10px 0px' : '-10px 0px 0px 0px', - }, - ); - intersectionObserver.observe(image); - - return () => intersectionObserver.unobserve(image); - }, [ - pageType, - chapterId, - chapterToOpenId, - isCurrentChapter, - isChapterToOpenVisible, - readingMode, - readingDirection, - openChapter, - image, - ]); + // gets immediately observed once on initial render + ignoreInitialObserve: true, + }), + [pageType], + ), + ); };