Drastically improve the loading speed of images in the ui by using a custom loader and abort controller to abort when images leave the view.

This commit is contained in:
Jaret Burkett
2026-05-25 10:04:25 -06:00
parent 7354def271
commit 083236a2a7
5 changed files with 203 additions and 57 deletions

View File

@@ -31,6 +31,11 @@ const SampleImageCard: React.FC<SampleImageCardProps> = ({
const videoRef = useRef<HTMLVideoElement | null>(null);
const [isVisible, setIsVisible] = useState(false);
const [loaded, setLoaded] = useState(false);
const [blobUrl, setBlobUrl] = useState<string | null>(null);
const isItAudio = isAudio(imageUrl);
const isItVideo = isVideo(imageUrl);
const isImageType = !isItAudio && !isItVideo;
// Observe both enter and exit
useEffect(() => {
@@ -56,9 +61,44 @@ const SampleImageCard: React.FC<SampleImageCardProps> = ({
return () => observer.disconnect();
}, [observerRoot, rootMargin]);
const handleLoad = () => setLoaded(true);
// Drive image loads through fetch + AbortController so scrolling past actually
// cancels in-flight requests (browsers don't reliably cancel <img> fetches when
// the element unmounts). A short debounce skips requests entirely during fast
// scrolls where the card is only briefly visible.
useEffect(() => {
if (!isImageType) return;
if (!isVisible) return;
const isImageType = !isAudio(imageUrl) && !isVideo(imageUrl);
const controller = new AbortController();
let cancelled = false;
let objectUrl: string | null = null;
const timer = window.setTimeout(() => {
fetch(`/api/img/${encodeURIComponent(imageUrl)}`, { signal: controller.signal })
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.blob();
})
.then(blob => {
if (cancelled) return;
objectUrl = URL.createObjectURL(blob);
setBlobUrl(objectUrl);
setLoaded(true);
})
.catch(err => {
if (err?.name !== 'AbortError') console.error('Sample image fetch failed:', err);
});
}, 80);
return () => {
cancelled = true;
clearTimeout(timer);
controller.abort();
if (objectUrl) URL.revokeObjectURL(objectUrl);
setBlobUrl(null);
setLoaded(false);
};
}, [isVisible, isImageType, imageUrl]);
return (
<div className={`flex flex-col ${className}`}>
@@ -69,7 +109,7 @@ const SampleImageCard: React.FC<SampleImageCardProps> = ({
}`}
>
{isVisible ? (
isAudio(imageUrl) ? (
isItAudio ? (
<div className="w-full h-full flex items-center justify-center bg-gray-900">
<img
src={`/api/audio/art/${encodeURIComponent(imageUrl)}`}
@@ -80,31 +120,28 @@ const SampleImageCard: React.FC<SampleImageCardProps> = ({
}}
/>
</div>
) : isVideo(imageUrl) ? (
) : isItVideo ? (
<video
ref={videoRef}
src={`/api/img/${encodeURIComponent(imageUrl)}`}
className="w-full h-full object-cover"
preload="none"
onLoad={handleLoad}
playsInline
muted
loop
autoPlay
controls={false}
/>
) : (
) : blobUrl ? (
<img
src={`/api/img/${encodeURIComponent(imageUrl)}`}
src={blobUrl}
alt={alt}
onLoad={handleLoad}
loading="lazy"
decoding="async"
className={`w-full h-full object-cover transition-opacity duration-300 ${
loaded ? 'opacity-100' : 'opacity-0'
}`}
/>
)
) : null
) : null}
{children && isVisible && <div className="absolute inset-0 flex items-center justify-center">{children}</div>}