Skip to content

Commit c8bae90

Browse files
committed
extract blur logic into ImageBlurPreloader component
1 parent 055fc37 commit c8bae90

File tree

3 files changed

+86
-26
lines changed

3 files changed

+86
-26
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import usePrevious from '@/components/react/hooks/usePrevious';
4+
import { cn } from '@/utils/styles';
5+
6+
import type { FC, ImgHTMLAttributes } from 'react';
7+
8+
interface Props extends ImgHTMLAttributes<HTMLImageElement> {
9+
blurSrc: string;
10+
className?: string;
11+
divClassName?: string;
12+
onSrcLoaded?: () => void;
13+
}
14+
15+
const initialSrc = '' as const;
16+
17+
const ImageBlurPreloader: FC<Props> = ({
18+
blurSrc = initialSrc,
19+
src = initialSrc,
20+
onSrcLoaded,
21+
className,
22+
divClassName,
23+
...props
24+
}) => {
25+
const [hasLoaded, setHasLoaded] = useState(false);
26+
const prevSrc = usePrevious(src);
27+
28+
// reset hasLoaded on src change
29+
useEffect(() => {
30+
if (prevSrc !== src) {
31+
setHasLoaded(false);
32+
}
33+
}, [prevSrc, src, setHasLoaded]);
34+
35+
// handle blur -> xl image switch
36+
useEffect(() => {
37+
if (!src) return;
38+
39+
const img = new Image();
40+
img.src = src;
41+
42+
img.onload = () => {
43+
setHasLoaded(true);
44+
onSrcLoaded?.();
45+
};
46+
}, [src, setHasLoaded]);
47+
48+
const imageSrc = hasLoaded ? src : blurSrc;
49+
50+
return (
51+
<>
52+
{imageSrc ? (
53+
<img {...props} src={imageSrc} className={cn('', className)} />
54+
) : (
55+
<div className={cn('aspect-video', divClassName)} />
56+
)}
57+
</>
58+
);
59+
};
60+
61+
export default ImageBlurPreloader;

src/components/react/ImageRandom.tsx

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useMemo, useState } from 'react';
22

3+
import ImageBlurPreloader from '@/components/react/ImageBlurPreloader';
34
import { getRandomElementFromArray } from '@/utils/array';
45
import { cn } from '@/utils/styles';
56

@@ -35,41 +36,24 @@ const ImageRandomReact: FC<Props> = ({ galleryImages, className, ...props }) =>
3536
setImage(randomImage);
3637
}, [setImage, randomImage]);
3738

38-
// handle blur -> xl image switch
39-
useEffect(() => {
40-
const img = new Image();
41-
img.src = image.xl.src;
42-
43-
img.onload = () => {
44-
setHasLoaded(true);
45-
};
46-
}, [image, setHasLoaded]);
47-
4839
const handleClick = async () => {
4940
const randomImage = getRandomElementFromArray(galleryImages);
5041
setImage(randomImage);
5142
setHasLoaded(false);
5243
};
5344

54-
const imageSrc = hasLoaded ? image.xl.src : image.blur.src;
5545
const imageAlt = hasLoaded ? 'Hero image' : 'Blur image';
5646

57-
// Todo: use <picture srcSet> for responsive images
58-
5947
return (
60-
<>
61-
{imageSrc ? (
62-
<img
63-
{...props}
64-
src={imageSrc}
65-
alt={imageAlt}
66-
onClick={handleClick}
67-
className={cn('cursor-pointer', className)}
68-
/>
69-
) : (
70-
<div className={cn('aspect-video', className)} />
71-
)}
72-
</>
48+
<ImageBlurPreloader
49+
{...props}
50+
blurSrc={image.blur.src}
51+
src={image.xl.src}
52+
onSrcLoaded={() => setHasLoaded(true)}
53+
alt={imageAlt}
54+
onClick={handleClick}
55+
className={cn('cursor-pointer', className)}
56+
/>
7357
);
7458
};
7559

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
import type { MutableRefObject } from 'react';
4+
5+
const usePrevious = function <T>(value: T): MutableRefObject<T | undefined>['current'] {
6+
const ref = useRef<T>();
7+
8+
useEffect(() => {
9+
ref.current = value;
10+
}, [value]);
11+
12+
return ref.current;
13+
};
14+
15+
export default usePrevious;

0 commit comments

Comments
 (0)