预加载和适配屏幕修复
This commit is contained in:
@@ -7,6 +7,7 @@ import { ArrowDownWideNarrow, ArrowUpWideNarrow } from 'lucide-react';
|
||||
|
||||
import { getAllMangaReadRecords, saveMangaReadRecord } from '@/lib/db.client';
|
||||
import type { MangaChapter, MangaDetail, MangaReadRecord } from '@/lib/manga.types';
|
||||
import { processImageUrl } from '@/lib/utils';
|
||||
|
||||
import ProxyImage from '@/components/ProxyImage';
|
||||
|
||||
@@ -17,6 +18,7 @@ const READ_MODE_STORAGE_KEY = 'mangaReadMode';
|
||||
const SCALE_MODE_STORAGE_KEY = 'mangaScaleMode';
|
||||
const PAGE_GAP_STORAGE_KEY = 'mangaPageGap';
|
||||
const SAVE_INTERVAL_MS = 10000;
|
||||
const PRELOAD_PAGE_COUNT = 5;
|
||||
|
||||
const READ_MODE_OPTIONS: Array<{ value: ReadMode; label: string }> = [
|
||||
{ value: 'single', label: '单页' },
|
||||
@@ -98,6 +100,7 @@ export default function MangaReadPage() {
|
||||
const previousReadModeRef = useRef<ReadMode>('vertical');
|
||||
const requestVerticalPageSyncRef = useRef<(() => void) | null>(null);
|
||||
const currentVerticalPageIndexRef = useRef(0);
|
||||
const preloadedImageUrlsRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const getCurrentVerticalPageIndex = () => {
|
||||
if (!verticalPageRefs.current.length) return 0;
|
||||
@@ -121,6 +124,17 @@ export default function MangaReadPage() {
|
||||
requestVerticalPageSyncRef.current?.();
|
||||
};
|
||||
|
||||
const getPreloadAnchorPage = () => (
|
||||
readMode === 'vertical' ? currentVerticalPageIndexRef.current : activePage
|
||||
);
|
||||
|
||||
const getImageLoadingStrategy = (index: number): 'eager' | 'lazy' => {
|
||||
const anchorPage = getPreloadAnchorPage();
|
||||
return index >= Math.max(anchorPage - 1, 0) && index <= anchorPage + PRELOAD_PAGE_COUNT
|
||||
? 'eager'
|
||||
: 'lazy';
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
const savedMode = window.localStorage.getItem(READ_MODE_STORAGE_KEY) as ReadMode | null;
|
||||
@@ -206,6 +220,7 @@ export default function MangaReadPage() {
|
||||
useEffect(() => {
|
||||
setActivePage(0);
|
||||
restoredChapterKeyRef.current = null;
|
||||
preloadedImageUrlsRef.current.clear();
|
||||
}, [chapterId]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -393,6 +408,23 @@ export default function MangaReadPage() {
|
||||
pendingRecordDirtyRef.current = true;
|
||||
}, [activePage, chapterId, chapterName, cover, mangaId, pages.length, readMode, sourceId, sourceName, title]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || !pages.length) return;
|
||||
|
||||
const anchorPage = getPreloadAnchorPage();
|
||||
const preloadTargets = pages.slice(anchorPage + 1, anchorPage + 1 + PRELOAD_PAGE_COUNT);
|
||||
|
||||
preloadTargets.forEach((page) => {
|
||||
const resolvedUrl = processImageUrl(page);
|
||||
if (!resolvedUrl || preloadedImageUrlsRef.current.has(resolvedUrl)) return;
|
||||
|
||||
const img = new window.Image();
|
||||
img.decoding = 'async';
|
||||
img.src = resolvedUrl;
|
||||
preloadedImageUrlsRef.current.add(resolvedUrl);
|
||||
});
|
||||
}, [activePage, pages, readMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mangaId || !sourceId || !chapterId) return;
|
||||
|
||||
@@ -576,12 +608,12 @@ export default function MangaReadPage() {
|
||||
|
||||
const imageClassName = useMemo(() => {
|
||||
if (scaleMode === 'original') {
|
||||
return 'mx-auto h-auto w-auto max-w-none object-none';
|
||||
return 'block mx-auto h-auto w-auto max-w-none object-none';
|
||||
}
|
||||
if (readMode === 'single' || readMode === 'double') {
|
||||
return 'mx-auto max-h-[calc(100vh-8rem)] w-auto max-w-full object-contain';
|
||||
return 'block h-auto w-full object-contain sm:mx-auto sm:max-h-[calc(100vh-8rem)] sm:w-auto sm:max-w-full';
|
||||
}
|
||||
return 'h-auto w-full object-contain';
|
||||
return 'block h-auto w-full object-contain';
|
||||
}, [readMode, scaleMode]);
|
||||
|
||||
const handleReaderClick = (event: MouseEvent<HTMLDivElement>) => {
|
||||
@@ -742,7 +774,7 @@ export default function MangaReadPage() {
|
||||
)}
|
||||
|
||||
<div
|
||||
className='relative min-h-[calc(100vh-5rem)] select-none px-2 py-3 sm:px-3'
|
||||
className='relative min-h-[calc(100vh-5rem)] select-none px-0 py-3 sm:px-3'
|
||||
onClick={handleReaderClick}
|
||||
>
|
||||
{showChapterComplete && (
|
||||
@@ -812,6 +844,7 @@ export default function MangaReadPage() {
|
||||
originalSrc={page}
|
||||
alt={`${chapterName}-${index + 1}`}
|
||||
className={imageClassName}
|
||||
loading={getImageLoadingStrategy(index)}
|
||||
onLoad={handleVerticalImageLoad}
|
||||
/>
|
||||
</div>
|
||||
@@ -824,9 +857,14 @@ export default function MangaReadPage() {
|
||||
style={{ gap: `${pageGap}px` }}
|
||||
>
|
||||
{pages.map((page, index) => (
|
||||
<div key={`${page}-${index}`} className='flex min-w-full snap-center items-center justify-center px-1'>
|
||||
<div key={`${page}-${index}`} className='flex min-w-full snap-center items-center justify-center px-1'>
|
||||
<div className='w-full overflow-hidden bg-gray-100 shadow-sm dark:bg-gray-900'>
|
||||
<ProxyImage originalSrc={page} alt={`${chapterName}-${index + 1}`} className={imageClassName} />
|
||||
<ProxyImage
|
||||
originalSrc={page}
|
||||
alt={`${chapterName}-${index + 1}`}
|
||||
className={imageClassName}
|
||||
loading={getImageLoadingStrategy(index)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -843,6 +881,7 @@ export default function MangaReadPage() {
|
||||
originalSrc={page}
|
||||
alt={`${chapterName}-${activePage + index + 1}`}
|
||||
className={imageClassName}
|
||||
loading='eager'
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -16,6 +16,8 @@ const ProxyImage: React.FC<ProxyImageProps> = ({
|
||||
displaySrc,
|
||||
retryDelay = 2000,
|
||||
retryOnError = true,
|
||||
loading = 'lazy',
|
||||
decoding = 'async',
|
||||
onError,
|
||||
src: _src,
|
||||
...props
|
||||
@@ -41,6 +43,8 @@ const ProxyImage: React.FC<ProxyImageProps> = ({
|
||||
<img
|
||||
{...props}
|
||||
src={displaySrc || processImageUrl(originalSrc)}
|
||||
loading={loading}
|
||||
decoding={decoding}
|
||||
onError={handleError}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -151,8 +151,10 @@ export default function MangaLayout({ children }: MangaLayoutProps) {
|
||||
</header>
|
||||
|
||||
<main
|
||||
className={`mx-auto max-w-7xl px-3 pt-20 sm:px-6 sm:pt-24 ${
|
||||
isReadingPage ? 'pb-24 sm:pb-28 lg:pb-10' : 'pb-[calc(5rem+env(safe-area-inset-bottom))] sm:pb-[calc(5rem+env(safe-area-inset-bottom))] lg:pb-10'
|
||||
className={`mx-auto max-w-7xl pt-20 sm:pt-24 ${
|
||||
isReadingPage
|
||||
? 'px-0 pb-24 sm:px-0 sm:pb-28 lg:pb-10'
|
||||
: 'px-3 pb-[calc(5rem+env(safe-area-inset-bottom))] sm:px-6 sm:pb-[calc(5rem+env(safe-area-inset-bottom))] lg:pb-10'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user