readest/apps/readest-app/src/app/reader/hooks/useScrollToItem.ts
Huang Xin ef97a8ed02
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (rust) (push) Waiting to run
PR checks / rust_lint (push) Waiting to run
PR checks / build_web_app (push) Waiting to run
PR checks / build_tauri_app (push) Waiting to run
Deploy to vercel on merge / build_and_deploy (push) Waiting to run
fix(ux): optimize scrolling UX for the bookshelf and sidebar content (#3849)
2026-04-12 20:52:12 +02:00

50 lines
1.6 KiB
TypeScript

import { useEffect, useMemo, useRef } from 'react';
import { BookProgress } from '@/types/book';
import { isCfiInLocation } from '@/utils/cfi';
const useScrollToItem = (
cfi: string,
progress: BookProgress | null,
isNearest: boolean = false,
) => {
const viewRef = useRef<HTMLLIElement | null>(null);
const isCurrent = useMemo(() => isCfiInLocation(cfi, progress?.location), [cfi, progress]);
const shouldScroll = isCurrent || isNearest;
useEffect(() => {
if (!viewRef.current || !shouldScroll) return;
const element = viewRef.current;
const rect = element.getBoundingClientRect();
// Find the actual scrollable container (OverlayScrollbars viewport)
const scrollContainer = element.closest('[data-overlayscrollbars-viewport]');
const containerRect = scrollContainer?.getBoundingClientRect();
const isVisible = containerRect
? rect.top >= containerRect.top && rect.bottom <= containerRect.bottom
: rect.top >= 0 && rect.bottom <= window.innerHeight;
if (!isVisible) {
const isEink = document.documentElement.getAttribute('data-eink') === 'true';
const containerCenter = containerRect
? (containerRect.top + containerRect.bottom) / 2
: window.innerHeight / 2;
const distance = Math.abs(rect.top - containerCenter);
const SMOOTH_THRESHOLD = 1000;
const behavior = isEink || distance > SMOOTH_THRESHOLD ? 'auto' : 'smooth';
element.scrollIntoView({ behavior, block: 'center' });
}
if (isCurrent) {
element.setAttribute('aria-current', 'page');
}
}, [shouldScroll, isCurrent]);
return { isCurrent, viewRef };
};
export default useScrollToItem;