feat: thumbnail and observer to load only viewed
This commit is contained in:
@@ -6,14 +6,24 @@ import { forwardRef, useEffect, useState, useCallback, useRef } from "react";
|
|||||||
|
|
||||||
export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
||||||
(
|
(
|
||||||
{ pageNumber, currentPage, onPageChange, getThumbnailUrl, loadedThumbnails, onThumbnailLoad },
|
{
|
||||||
|
pageNumber,
|
||||||
|
currentPage,
|
||||||
|
onPageChange,
|
||||||
|
getThumbnailUrl,
|
||||||
|
loadedThumbnails,
|
||||||
|
onThumbnailLoad,
|
||||||
|
isVisible,
|
||||||
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
|
const [isInViewport, setIsInViewport] = useState(false);
|
||||||
const loadAttempts = useRef(0);
|
const loadAttempts = useRef(0);
|
||||||
const maxAttempts = 3;
|
const maxAttempts = 3;
|
||||||
|
const thumbnailRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
const resetLoadingState = useCallback(() => {
|
const resetLoadingState = useCallback(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -21,7 +31,37 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
|||||||
loadAttempts.current = 0;
|
loadAttempts.current = 0;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Observer pour détecter quand la thumbnail est dans le viewport
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
//if (!isVisible) return; // Ne pas observer si la barre est cachée
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
setIsInViewport(entry.isIntersecting);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rootMargin: "50px",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const element = thumbnailRef.current;
|
||||||
|
if (element) {
|
||||||
|
observer.observe(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (element) {
|
||||||
|
observer.unobserve(element);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isVisible]);
|
||||||
|
|
||||||
|
// Charger l'image uniquement quand la thumbnail est dans le viewport
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isInViewport) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = getThumbnailUrl(pageNumber);
|
const url = getThumbnailUrl(pageNumber);
|
||||||
setImageUrl(url);
|
setImageUrl(url);
|
||||||
@@ -36,7 +76,7 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
|||||||
setHasError(true);
|
setHasError(true);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [pageNumber, getThumbnailUrl, loadedThumbnails, resetLoadingState]);
|
}, [pageNumber, getThumbnailUrl, loadedThumbnails, resetLoadingState, isVisible, isInViewport]);
|
||||||
|
|
||||||
const handleImageLoad = useCallback(() => {
|
const handleImageLoad = useCallback(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -65,7 +105,14 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
ref={ref}
|
ref={(node) => {
|
||||||
|
thumbnailRef.current = node;
|
||||||
|
if (typeof ref === "function") {
|
||||||
|
ref(node);
|
||||||
|
} else if (ref) {
|
||||||
|
ref.current = node;
|
||||||
|
}
|
||||||
|
}}
|
||||||
data-page={pageNumber}
|
data-page={pageNumber}
|
||||||
id={`thumbnail-${pageNumber}`}
|
id={`thumbnail-${pageNumber}`}
|
||||||
onClick={() => onPageChange(pageNumber)}
|
onClick={() => onPageChange(pageNumber)}
|
||||||
|
|||||||
Reference in New Issue
Block a user