Give cached fallback images 200ms to load

This commit is contained in:
Sam Becker 2025-06-24 20:58:07 -05:00
parent 462ac1a573
commit 8ea82e8c09

View File

@ -7,6 +7,9 @@ import { clsx} from 'clsx/lite';
import Image, { ImageProps } from 'next/image'; import Image, { ImageProps } from 'next/image';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
// If image is still loading after 200ms, force CSS animation
const FALLBACK_FADE_CUTOFF = 200;
export default function ImageWithFallback({ export default function ImageWithFallback({
className, className,
classNameImage = 'object-cover h-full', classNameImage = 'object-cover h-full',
@ -31,6 +34,7 @@ export default function ImageWithFallback({
const onLoad = useCallback(() => setIsLoading(false), []); const onLoad = useCallback(() => setIsLoading(false), []);
const onError = useCallback(() => setDidError(true), []); const onError = useCallback(() => setDidError(true), []);
const [forceFallbackFade, setForceFallbackFade] = useState(false);
const [hideFallback, setHideFallback] = useState(false); const [hideFallback, setHideFallback] = useState(false);
const refImage = useRef<HTMLImageElement>(null); const refImage = useRef<HTMLImageElement>(null);
@ -50,16 +54,15 @@ export default function ImageWithFallback({
isLoadingRef.current = isLoading; isLoadingRef.current = isLoading;
}, [isLoading]); }, [isLoading]);
const forceTransition = useRef(false);
useEffect(() => { useEffect(() => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
// If image is still loading, force CSS animation // If image is still loading, force CSS animation
if (isLoadingRef.current) { if (isLoadingRef.current) {
forceTransition.current = true; setForceFallbackFade(true);
} }
}, 200); }, FALLBACK_FADE_CUTOFF);
return () => clearTimeout(timeout); return () => clearTimeout(timeout);
}, [shouldDebugFallbackTiming]); }, []);
const timeStartRef = useRef(performance.now()); const timeStartRef = useRef(performance.now());
useEffect(() => { useEffect(() => {
@ -81,7 +84,7 @@ export default function ImageWithFallback({
wasCached: wasCachedRef.current, wasCached: wasCachedRef.current,
hideFallback, hideFallback,
showFallback, showFallback,
forceTransition: forceTransition.current, forceTransition: forceFallbackFade,
}); });
} }
@ -174,7 +177,7 @@ export default function ImageWithFallback({
'@container', '@container',
'absolute inset-0 pointer-events-none', 'absolute inset-0 pointer-events-none',
'overflow-hidden', 'overflow-hidden',
(showFallback || shouldDebugImageFallbacks) && ((showFallback && !forceFallbackFade) || shouldDebugImageFallbacks) &&
'transition-opacity duration-300 ease-in', 'transition-opacity duration-300 ease-in',
!(BLUR_ENABLED && blurDataURL) && 'bg-main', !(BLUR_ENABLED && blurDataURL) && 'bg-main',
(isLoading || shouldDebugImageFallbacks) (isLoading || shouldDebugImageFallbacks)