Skip to content

Commit 00b7000

Browse files
committed
Optimize ImageComparisonSlider component with memoization and callback hooks
1 parent 04701b0 commit 00b7000

File tree

1 file changed

+202
-95
lines changed

1 file changed

+202
-95
lines changed

src/ImageComparisonSlider.tsx

+202-95
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useRef, TouchEvent } from 'react';
1+
import React, { useState, useRef, TouchEvent, useEffect, useCallback } from 'react';
22

33
interface AppProps {
44
originalImageSrc: string;
@@ -7,107 +7,214 @@ interface AppProps {
77
sliderColor: string;
88
}
99

10-
function ImageComparisonSlider({
11-
originalImageSrc,
12-
upscaledImageSrc,
13-
sliderPosition,
14-
sliderColor,
15-
}: AppProps) {
16-
const [imageRevealFraction, setImageRevealFraction] = useState(sliderPosition);
17-
const imageContainer = useRef<HTMLDivElement>(null);
18-
19-
const slide = (xPosition: number): void => {
20-
const containerBoundingRect = imageContainer.current?.getBoundingClientRect();
21-
22-
if (containerBoundingRect) {
23-
setImageRevealFraction(() => {
24-
if (xPosition < containerBoundingRect.left) {
25-
return 0;
26-
} else if (xPosition > containerBoundingRect.right) {
27-
return 1;
28-
}
29-
return (xPosition - containerBoundingRect.left) / containerBoundingRect.width;
30-
});
31-
}
32-
};
33-
34-
const handleTouchMove = (event: TouchEvent<HTMLDivElement>): void => {
35-
slide(event.touches.item(0)?.clientX || 0);
36-
};
37-
38-
const handleMouseDown = (): void => {
39-
window.onmousemove = handleMouseMoveMove;
40-
window.onmouseup = handleMouseUp;
41-
};
42-
43-
const handleMouseMoveMove = (event: MouseEvent): void => {
44-
slide(event.clientX);
45-
};
46-
47-
const handleMouseUp = (): void => {
48-
window.onmousemove = null;
49-
window.onmouseup = null;
50-
};
51-
52-
return (
53-
<div className='px4'>
54-
<div
55-
ref={imageContainer}
56-
className='max-w-lg w-full mx-auto mt-32 relative select-none group'
57-
>
58-
<img src={originalImageSrc} alt='Original' className='pointer-events-none' />
59-
<img
60-
style={{
61-
filter: 'grayscale(100%)',
62-
clipPath: `polygon(0 0, ${imageRevealFraction * 100}% 0, ${
63-
imageRevealFraction * 100
64-
}% 100%, 0% 100%)`,
65-
}}
66-
src={upscaledImageSrc}
67-
alt='Upscaled'
68-
className='absolute inset-0 pointer-events-none'
69-
/>
10+
const ImageComparisonSlider = React.memo(
11+
({ originalImageSrc, upscaledImageSrc, sliderPosition, sliderColor }: AppProps) => {
12+
const [imageRevealFraction, setImageRevealFraction] = useState(sliderPosition);
13+
const imageContainer = useRef<HTMLDivElement>(null);
7014

15+
const slide = useCallback((xPosition: number): void => {
16+
const containerBoundingRect = imageContainer.current?.getBoundingClientRect();
17+
18+
if (containerBoundingRect) {
19+
setImageRevealFraction(() => {
20+
if (xPosition < containerBoundingRect.left) {
21+
return 0;
22+
} else if (xPosition > containerBoundingRect.right) {
23+
return 1;
24+
}
25+
return (xPosition - containerBoundingRect.left) / containerBoundingRect.width;
26+
});
27+
}
28+
}, []);
29+
30+
const handleTouchMove = useCallback(
31+
(event: TouchEvent<HTMLDivElement>): void => {
32+
slide(event.touches.item(0)?.clientX || 0);
33+
},
34+
[slide]
35+
);
36+
37+
const handleMouseMoveMove = useCallback(
38+
(event: MouseEvent): void => {
39+
slide(event.clientX);
40+
},
41+
[slide]
42+
);
43+
44+
const handleMouseUp = useCallback((): void => {
45+
window.onmousemove = null;
46+
window.onmouseup = null;
47+
}, []);
48+
49+
const handleMouseDown = useCallback((): void => {
50+
window.onmousemove = handleMouseMoveMove;
51+
window.onmouseup = handleMouseUp;
52+
}, [handleMouseMoveMove, handleMouseUp]);
53+
54+
useEffect(() => {
55+
return () => {
56+
window.onmousemove = null;
57+
window.onmouseup = null;
58+
};
59+
}, []);
60+
61+
return (
62+
<div className='px4'>
7163
<div
72-
style={{ left: `${imageRevealFraction * 100}%` }}
73-
className='absolute inset-0 group-hover:opacity-0 group-hover:opacity-100 transition-opacity duration-300 opacity-0'
64+
ref={imageContainer}
65+
className='max-w-lg w-full mx-auto mt-32 relative select-none group'
7466
>
75-
<div className='relative h-full'>
76-
<div
77-
className='absolute inset-0'
78-
style={{
79-
backgroundColor: sliderColor,
80-
width: '0.5px',
81-
marginLeft: '-1px',
82-
opacity: '50',
83-
}}
84-
></div>
85-
<div
86-
style={{ touchAction: 'none' }}
87-
onMouseDown={handleMouseDown}
88-
onTouchMove={handleTouchMove}
89-
className='h-12 w-12 -ml-6 -mt-6 rounded-full bg-white absolute top-1/2'
90-
>
91-
<svg
92-
xmlns='http://www.w3.org/2000/svg'
93-
fill='none'
94-
viewBox='0 0 24 24'
95-
strokeWidth={1.5}
96-
stroke='currentColor'
97-
className='w-6 h-6 text-gray-800 rotate-90 cursor-pointer absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2'
67+
<img src={originalImageSrc} alt='Original' className='pointer-events-none' />
68+
<img
69+
style={{
70+
filter: 'grayscale(100%)',
71+
clipPath: `polygon(0 0, ${imageRevealFraction * 100}% 0, ${
72+
imageRevealFraction * 100
73+
}% 100%, 0% 100%)`,
74+
}}
75+
src={upscaledImageSrc}
76+
alt='Upscaled'
77+
className='absolute inset-0 pointer-events-none'
78+
/>
79+
80+
<div
81+
style={{ left: `${imageRevealFraction * 100}%` }}
82+
className='absolute inset-0 group-hover:opacity-0 group-hover:opacity-100 transition-opacity duration-300 opacity-0'
83+
>
84+
<div className='relative h-full'>
85+
<div
86+
className='absolute inset-0'
87+
style={{
88+
backgroundColor: sliderColor,
89+
width: '0.5px',
90+
marginLeft: '-1px',
91+
opacity: '50',
92+
}}
93+
></div>
94+
<div
95+
style={{ touchAction: 'none' }}
96+
onMouseDown={handleMouseDown}
97+
onTouchMove={handleTouchMove}
98+
className='h-12 w-12 -ml-6 -mt-6 rounded-full bg-white absolute top-1/2'
9899
>
99-
<path
100-
strokeLinecap='round'
101-
strokeLinejoin='round'
102-
d='M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9'
103-
/>
104-
</svg>
100+
<svg
101+
xmlns='http://www.w3.org/2000/svg'
102+
fill='none'
103+
viewBox='0 0 24 24'
104+
strokeWidth={1.5}
105+
stroke='currentColor'
106+
className='w-6 h-6 text-gray-800 rotate-90 cursor-pointer absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2'
107+
>
108+
<path
109+
strokeLinecap='round'
110+
strokeLinejoin='round'
111+
d='M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9'
112+
/>
113+
//{' '}
114+
</svg>
115+
</div>
105116
</div>
106117
</div>
107118
</div>
108119
</div>
109-
</div>
110-
);
111-
}
120+
);
121+
}
122+
);
112123

113124
export default ImageComparisonSlider;
125+
126+
// function ImageComparisonSlider({ originalImageSrc, upscaledImageSrc, sliderPosition, sliderColor }: AppProps) {
127+
// const [imageRevealFraction, setImageRevealFraction] = useState(sliderPosition);
128+
// const imageContainer = useRef<HTMLDivElement>(null);
129+
130+
// const slide = (xPosition: number): void => {
131+
// const containerBoundingRect = imageContainer.current?.getBoundingClientRect();
132+
133+
// if (containerBoundingRect) {
134+
// setImageRevealFraction(() => {
135+
// if (xPosition < containerBoundingRect.left) {
136+
// return 0;
137+
// } else if (xPosition > containerBoundingRect.right) {
138+
// return 1;
139+
// }
140+
// return (xPosition - containerBoundingRect.left) / containerBoundingRect.width;
141+
// });
142+
// }
143+
// };
144+
145+
// const handleTouchMove = (event: TouchEvent<HTMLDivElement>): void => {
146+
// slide(event.touches.item(0)?.clientX || 0);
147+
// };
148+
149+
// const handleMouseDown = (): void => {
150+
// window.onmousemove = handleMouseMoveMove;
151+
// window.onmouseup = handleMouseUp;
152+
// };
153+
154+
// const handleMouseMoveMove = (event: MouseEvent): void => {
155+
// slide(event.clientX);
156+
// };
157+
158+
// const handleMouseUp = (): void => {
159+
// window.onmousemove = null;
160+
// window.onmouseup = null;
161+
// };
162+
163+
// return (
164+
// <div className='px4'>
165+
// <div
166+
// ref={imageContainer}
167+
// className='max-w-lg w-full mx-auto mt-32 relative select-none group'
168+
// >
169+
// <img src={originalImageSrc} alt='Original' className='pointer-events-none' />
170+
// <img
171+
// style={{
172+
// filter: 'grayscale(100%)',
173+
// clipPath: `polygon(0 0, ${imageRevealFraction * 100}% 0, ${imageRevealFraction * 100}% 100%, 0% 100%)`,
174+
// }}
175+
// src={upscaledImageSrc}
176+
// alt='Upscaled'
177+
// className='absolute inset-0 pointer-events-none'
178+
// />
179+
180+
// <div
181+
// style={{ left: `${imageRevealFraction * 100}%` }}
182+
// className='absolute inset-0 group-hover:opacity-0 group-hover:opacity-100 transition-opacity duration-300 opacity-0'
183+
// >
184+
// <div className='relative h-full'>
185+
// <div
186+
// className='absolute inset-0'
187+
// style={{
188+
// backgroundColor: sliderColor,
189+
// width: '0.5px',
190+
// marginLeft: '-1px',
191+
// opacity: '50',
192+
// }}
193+
// ></div>
194+
// <div
195+
// style={{ touchAction: 'none' }}
196+
// onMouseDown={handleMouseDown}
197+
// onTouchMove={handleTouchMove}
198+
// className='h-12 w-12 -ml-6 -mt-6 rounded-full bg-white absolute top-1/2'
199+
// >
200+
// <svg
201+
// xmlns='http://www.w3.org/2000/svg'
202+
// fill='none'
203+
// viewBox='0 0 24 24'
204+
// strokeWidth={1.5}
205+
// stroke='currentColor'
206+
// className='w-6 h-6 text-gray-800 rotate-90 cursor-pointer absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2'
207+
// >
208+
// <path
209+
// strokeLinecap='round'
210+
// strokeLinejoin='round'
211+
// d='M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9'
212+
// />
213+
// </svg>
214+
// </div>
215+
// </div>
216+
// </div>
217+
// </div>
218+
// </div>
219+
// );
220+
// }

0 commit comments

Comments
 (0)