Skip to content

Commit 84d03b3

Browse files
committed
Enhanced testimonial UI with parallax effect and fixed React hook dependency warnings by implementing useCallback.
1 parent db32221 commit 84d03b3

File tree

3 files changed

+106
-28
lines changed

3 files changed

+106
-28
lines changed
1.62 MB
Loading
-1.15 MB
Binary file not shown.

src/components/testimonial-section/Testimonial.jsx

Lines changed: 106 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { useEffect, useState } from "react";
1+
import { useEffect, useState, useRef, useCallback } from "react";
22

33
import { BsChevronLeft, BsChevronRight } from "react-icons/bs";
44
import { RxDotFilled } from "react-icons/rx";
55

66
import person01 from "../../assets/Images/person01.png";
77
import person02 from "../../assets/Images/person02.png";
88
import person03 from "../../assets/Images/person03.png";
9-
import testimonialbg from "../../assets/Images/testimonialbg.png";
9+
import testimonialbg from "../../assets/Images/testimonialbg.jpg";
1010

1111
const slides = [
1212
{
@@ -31,20 +31,58 @@ const slides = [
3131

3232
const Testimonial = () => {
3333
const [slide, setSlide] = useState(0);
34+
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
35+
const testimonialRef = useRef(null);
36+
37+
const handleRightSlide = useCallback(() => {
38+
const isLastSlide = slide === slides.length - 1;
39+
const nextSlide = isLastSlide ? 0 : slide + 1;
40+
setSlide(nextSlide);
41+
}, [slide]);
3442

3543
useEffect(() => {
3644
const interval = setInterval(() => {
3745
handleRightSlide();
3846
}, 3000);
3947

4048
return () => clearInterval(interval);
41-
}, [slide]);
49+
}, [handleRightSlide]);
50+
51+
useEffect(() => {
52+
const handleMouseMove = (e) => {
53+
if (testimonialRef.current) {
54+
const rect = testimonialRef.current.getBoundingClientRect();
55+
// Calculate mouse position relative to the component
56+
const x = e.clientX - rect.left;
57+
const y = e.clientY - rect.top;
58+
setMousePosition({ x, y });
59+
}
60+
};
61+
62+
window.addEventListener("mousemove", handleMouseMove);
63+
return () => {
64+
window.removeEventListener("mousemove", handleMouseMove);
65+
};
66+
}, []);
67+
68+
// Calculate parallax values
69+
const calculateParallax = (strength = 20) => {
70+
if (!testimonialRef.current) return { x: 0, y: 0 };
71+
72+
const rect = testimonialRef.current.getBoundingClientRect();
73+
const centerX = rect.width / 2;
74+
const centerY = rect.height / 2;
75+
76+
// Calculate offset from center (normalized between -1 and 1)
77+
const offsetX = (mousePosition.x - centerX) / centerX;
78+
const offsetY = (mousePosition.y - centerY) / centerY;
79+
80+
return {
81+
x: offsetX * strength,
82+
y: offsetY * strength,
83+
};
84+
};
4285

43-
function handleRightSlide() {
44-
const isLastSlide = slide === slides.length - 1;
45-
const nextSlide = isLastSlide ? 0 : slide + 1;
46-
setSlide(nextSlide);
47-
}
4886
function handleLeftSlide() {
4987
const isFirstSlide = slide === 0;
5088
const newSlide = isFirstSlide ? slides.length - 1 : slide - 1;
@@ -54,40 +92,74 @@ const Testimonial = () => {
5492
function goToSlide(slideIndex) {
5593
setSlide(slideIndex);
5694
}
95+
96+
const parallax = calculateParallax();
97+
5798
return (
5899
<div
59-
className=" testimonial max-w-[1440px] h-[780px] m-auto py-16 px-4 relative group"
100+
ref={testimonialRef}
101+
className="testimonial max-w-[1440px] h-[700px] m-auto py-16 px-4 relative group overflow-hidden"
60102
style={{ backgroundImage: `url(${testimonialbg})` }}
61103
>
62-
<h2 className="text-4xl font-bold tracking-wider text-textWhite mb-[2rem] text-center font-monsterrat">
104+
<h2
105+
className="text-4xl font-bold tracking-wider text-textWhite mb-[2rem] text-center font-monsterrat"
106+
style={{
107+
transform: `translate(${parallax.x * -0.5}px, ${parallax.y * -0.5}px)`,
108+
transition: "transform 0.1s ease-out",
109+
}}
110+
>
63111
Testimonials
64112
</h2>
65-
<div className="flex justify-center items-center gap-4 sm:gap-20">
66-
<div className="flex justify-center items-center w-40 h-40 sm:w-40 sm:h-40 rounded-full overflow-hidden py-3 bg-center bg-cover duration-500">
113+
<div
114+
className="flex justify-center items-center gap-4 sm:gap-20"
115+
style={{
116+
transform: `translate(${parallax.x * -1}px, ${parallax.y * -1}px)`,
117+
transition: "transform 0.1s ease-out",
118+
}}
119+
>
120+
<div
121+
className="flex justify-center items-center w-40 h-40 sm:w-40 sm:h-40 rounded-full overflow-hidden py-3 bg-center bg-cover duration-500"
122+
style={{
123+
transform: `translate(${parallax.x * 2}px, ${parallax.y * 1.2}px)`,
124+
transition: "transform 0.1s ease-out",
125+
}}
126+
>
67127
<img
68128
src={slides[slide - 1 < 0 ? slides.length - 1 : slide - 1].url}
69129
alt="image"
70-
style={{ marginBottom: '0' }}
71-
/*
72-
Warning: A 60px margin is applied to all images in home.css,
73-
causing layout issues. Remove if unnecessary,
74-
or use overrides for specific components.
75-
That's what I did overrided specific components becouse I don't know it that home.css code is needed or not.
76-
*/
77-
130+
style={{ marginBottom: "0" }}
78131
/>
79132
</div>
80-
<div className="flex justify-center items-center w-56 h-56 sm:w-96 sm:h-96 rounded-full overflow-hidden py-3 bg-center bg-cover duration-500 hover:scale-110 cursor-pointer">
133+
<div
134+
className="flex justify-center items-center w-56 h-56 sm:w-96 sm:h-96 rounded-full overflow-hidden py-3 bg-center bg-cover duration-500 hover:scale-110 cursor-pointer"
135+
style={{
136+
transform: `translate(${parallax.x * -1.5}px, ${parallax.y * -1.5}px)`,
137+
transition: "transform 0.1s ease-out, scale 0.3s ease",
138+
}}
139+
>
81140
<img src={slides[slide].url} alt="image" />
82141
</div>
83-
<div className="flex justify-center items-center w-40 h-40 sm:w-40 sm:h-40 rounded-full overflow-hidden py-3 bg-center bg-cover duration-500">
84-
<img src={slides[slide + 1 > slides.length - 1 ? 0 : slide + 1].url} alt="image"
85-
style={{ marginBottom: '0' }}
86-
/>
142+
<div
143+
className="flex justify-center items-center w-40 h-40 sm:w-40 sm:h-40 rounded-full overflow-hidden py-3 bg-center bg-cover duration-500"
144+
style={{
145+
transform: `translate(${parallax.x * 2}px, ${parallax.y * 1.2}px)`,
146+
transition: "transform 0.1s ease-out",
147+
}}
148+
>
149+
<img
150+
src={slides[slide + 1 > slides.length - 1 ? 0 : slide + 1].url}
151+
alt="image"
152+
style={{ marginBottom: "0" }}
153+
/>
87154
</div>
88-
89155
</div>
90-
<div className="flex flex-col mx-auto w-[70%] justify-center text-center text-textWhite font-medium">
156+
<div
157+
className="flex flex-col mx-auto w-[70%] justify-center text-center text-textWhite font-medium"
158+
style={{
159+
transform: `translate(${parallax.x * -0.8}px, ${parallax.y * -0.8}px)`,
160+
transition: "transform 0.1s ease-out",
161+
}}
162+
>
91163
<h2>{slides[slide].name}</h2>
92164
<p className="py-4">{slides[slide].paragraph}</p>
93165
</div>
@@ -104,7 +176,13 @@ const Testimonial = () => {
104176
>
105177
<BsChevronRight size={30} />
106178
</div>
107-
<div className="flex justify-center items-center py-2">
179+
<div
180+
className="flex justify-center items-center py-2"
181+
style={{
182+
transform: `translate(${parallax.x * -0.3}px, ${parallax.y * -0.3}px)`,
183+
transition: "transform 0.1s ease-out",
184+
}}
185+
>
108186
{slides.map((singleSlide, slideIndex) => (
109187
<div
110188
key={slideIndex}

0 commit comments

Comments
 (0)