From c41ef094eacacd77afc0258b20c1ef4388403779 Mon Sep 17 00:00:00 2001 From: nyne Date: Sun, 23 Nov 2025 16:41:47 +0800 Subject: [PATCH] feat: implement fullscreen gallery with image preloading and navigation --- frontend/src/pages/resource_details_page.tsx | 187 +++++++++++++++++-- 1 file changed, 168 insertions(+), 19 deletions(-) diff --git a/frontend/src/pages/resource_details_page.tsx b/frontend/src/pages/resource_details_page.tsx index 18c96b6..19dd6e2 100644 --- a/frontend/src/pages/resource_details_page.tsx +++ b/frontend/src/pages/resource_details_page.tsx @@ -34,6 +34,7 @@ import { MdOutlineArticle, MdOutlineChevronLeft, MdOutlineChevronRight, + MdOutlineClose, MdOutlineCloud, MdOutlineComment, MdOutlineContentCopy, @@ -2071,6 +2072,17 @@ function Gallery({ images, nsfw }: { images: number[], nsfw: number[] }) { }; }, []); + // 预加载下一张图片 + useEffect(() => { + if (images.length <= 1) return; + + const nextIndex = (currentIndex + 1) % images.length; + const nextImageUrl = network.getImageUrl(images[nextIndex]); + + const img = new Image(); + img.src = nextImageUrl; + }, [currentIndex, images]); + if (!images || images.length === 0) { return <>; } @@ -2099,25 +2111,16 @@ function Gallery({ images, nsfw }: { images: number[], nsfw: number[] }) { return ( <> - { - dialogRef.current?.close(); - }} - className="modal" - > -
- -
-
+
; + images: number[]; + currentIndex: number; + direction: number; + isHovered: boolean; + setIsHovered: (hovered: boolean) => void; + goToPrevious: () => void; + goToNext: () => void; +}) { + const [width, setWidth] = useState(0); + const containerRef = useRef(null); + + useEffect(() => { + const updateWidth = () => { + if (containerRef.current) { + console.log(containerRef.current.clientWidth); + setWidth(containerRef.current.clientWidth); + } + }; + updateWidth(); + window.addEventListener("resize", updateWidth); + return () => { + window.removeEventListener("resize", updateWidth); + }; + }, []); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (dialogRef.current?.open) { + if (e.key === "ArrowLeft") { + e.preventDefault(); + goToPrevious(); + } else if (e.key === "ArrowRight") { + e.preventDefault(); + goToNext(); + } else if (e.key === "Escape") { + dialogRef.current?.close(); + } + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [dialogRef, goToPrevious, goToNext]); + + return ( + { + dialogRef.current?.close(); + }} + className="modal" + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > +
+ {width > 0 && + ({ + x: dir > 0 ? width : -width, + }), + center: { + x: 0, + transition: { duration: 0.3, ease: "linear" }, + }, + exit: (dir: number) => ({ + x: dir > 0 ? -width : width, + transition: { duration: 0.3, ease: "linear" }, + }), + }} + initial="enter" + animate="center" + exit="exit" + custom={direction} + > + + + } + + {/* 全屏模式下的左右切换按钮 */} + {images.length > 1 && ( + <> + + + + {/* 全屏模式下的指示器 */} +
+
+ {currentIndex + 1} / {images.length} +
+
+ + {/* 关闭按钮 */} + + + )} +
+
+ ); +} + function GalleryImage({src, nfsw}: {src: string, nfsw: boolean}) { const [show, setShow] = useState(!nfsw);