From 5598eac3d01a553b00152e0bd54fb04ccb98f537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youn=20M=C3=A9lois?= Date: Mon, 11 Nov 2024 00:56:23 +0100 Subject: [PATCH] feat: preload images when near view Fixes #8 --- deno.json | 1 - deno.lock | 10 ------ src/pages/Browse/ExtensionBrowse.tsx | 11 +++--- src/utils/lazy-image-hook.ts | 53 ++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/utils/lazy-image-hook.ts diff --git a/deno.json b/deno.json index f852d0d..51ab244 100644 --- a/deno.json +++ b/deno.json @@ -32,7 +32,6 @@ "react": "npm:react@18.3.1", "react-dom": "npm:react-dom@18.3.1", "react-icons": "npm:react-icons@5.3.0", - "react-intersection-observer": "npm:react-intersection-observer@9.13.1", "react-router-dom": "npm:react-router-dom@6.27.0", "tailwindcss": "npm:tailwindcss@3.4.14", "vite": "npm:vite@5.4.9", diff --git a/deno.lock b/deno.lock index f8e567e..ca640d4 100644 --- a/deno.lock +++ b/deno.lock @@ -16,8 +16,6 @@ "npm:react-dom@18.3.1": "18.3.1_react@18.3.1", "npm:react-icons@5.3.0": "5.3.0_react@18.3.1", "npm:react-infinite-scroller@^1.2.6": "1.2.6_react@18.3.1", - "npm:react-intersection-observer@9.13.1": "9.13.1_react@18.3.1_react-dom@18.3.1__react@18.3.1", - "npm:react-intersection-observer@^9.13.1": "9.13.1_react@18.3.1_react-dom@18.3.1__react@18.3.1", "npm:react-router-dom@6.27.0": "6.27.0_react@18.3.1_react-dom@18.3.1__react@18.3.1", "npm:react@18.3.1": "18.3.1", "npm:tailwindcss@3.4.14": "3.4.14_postcss@8.4.47", @@ -939,13 +937,6 @@ "react" ] }, - "react-intersection-observer@9.13.1_react@18.3.1_react-dom@18.3.1__react@18.3.1": { - "integrity": "sha512-tSzDaTy0qwNPLJHg8XZhlyHTgGW6drFKTtvjdL+p6um12rcnp8Z5XstE+QNBJ7c64n5o0Lj4ilUleA41bmDoMw==", - "dependencies": [ - "react", - "react-dom" - ] - }, "react-is@16.13.1": { "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, @@ -1215,7 +1206,6 @@ "npm:postcss@8.4.47", "npm:react-dom@18.3.1", "npm:react-icons@5.3.0", - "npm:react-intersection-observer@9.13.1", "npm:react-router-dom@6.27.0", "npm:react@18.3.1", "npm:tailwindcss@3.4.14", diff --git a/src/pages/Browse/ExtensionBrowse.tsx b/src/pages/Browse/ExtensionBrowse.tsx index 7c63b26..4231143 100644 --- a/src/pages/Browse/ExtensionBrowse.tsx +++ b/src/pages/Browse/ExtensionBrowse.tsx @@ -1,6 +1,5 @@ import { forwardRef, useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; -import { useInView } from "react-intersection-observer"; import { Extension } from "../../types/extension.ts"; import { Manga } from "../../types/manga.ts"; @@ -8,6 +7,7 @@ import { getMangaList } from "../../services/extensions.service.ts"; import { useStore } from "../../services/store.service.ts"; import { downloadImage } from "../../services/tauri.service.ts"; import useInfiniteScroll from "../../utils/infinite-scroll-hook.ts"; +import useLazyImage from "../../utils/lazy-image-hook.ts"; export default function ExtensionBrowse() { const { extensionId } = useParams(); @@ -116,10 +116,11 @@ const MangaItem = ( const [src, setSrc] = useState(null); const [loading, setLoading] = useState(true); - const { ref, inView } = useInView({ + const { containerRef, inView } = useLazyImage({ onChange: (inView) => { if (inView) setLoading(true); }, + offset: "200vh", }); useEffect(() => { @@ -133,11 +134,13 @@ const MangaItem = ( return ( <> -
+
{inView && ( {}, + offset = "0px", +}: { + onChange?: (inView: boolean) => void; + offset?: string; +}) { + const [inView, setInView] = useState(false); + const observerRef = useRef(); + const targetRef = useRef((() => { + const target = document.createElement("div"); + target.setAttribute("image-lazy-loading-detector", ""); + target.style.position = "absolute"; + target.style.bottom = "0px"; + target.style.width = "0px"; + target.style.height = `calc(100% + ${offset} * 2)`; + target.style.top = `calc(${offset} * -1)`; + return target; + })()); + + const containerRef: LegacyRef = (container: HTMLElement) => { + if (container) { + container.append(targetRef.current); + container.style.position = "relative"; + } + }; + + useEffect(() => { + onChange(inView); + }, [inView]); + + useEffect(() => { + const observer = observerRef.current; + + if (observer) { + observer.disconnect(); + } + + observerRef.current = new IntersectionObserver(([entry]) => { + setInView(entry.isIntersecting); + }); + + observerRef.current.observe(targetRef.current); + + return () => { + observerRef.current?.disconnect(); + }; + }, []); + + return { containerRef, inView }; +}