I am trying to recreate a similar effect that Netflix's current UI has. i.e., I want a user to hover over a card, and if they keep their mouse on the card for more than 1 sec, the content (Video image section) should be revealed while scaling and going from opacity Zero to 1 slowly. I am able to achieve the required Animation but the problem with the UI is that it automatically sets isHovered state to True, even when the user has left the Card.
export default function Card({ movieData, isLiked = false }) {
const [isHovered, setIsHovered] = useState(false);
console.log("movdata");
const nav = useNavigate();
let timeoutId = null;
const handleMouseEnter = () => {
timeoutId = setTimeout(() => {
setIsHovered(true);
}, 1000);
};
const handleMouseLeave = () => {
clearTimeout(timeoutId);
setIsHovered(false);
};
return (
<Container
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={isHovered ? "hovered" : ""}
>
<img
src={`https://image.tmdb.org/t/p/w500${movieData.image}`}
alt="age"
/>
<div
className="hover rr"
style={{
visibility: `${isHovered === true ? "visible" : "hidden"}`,
opacity: `${isHovered === true ? "1" : "0"}`,
}}
>
{
<>
<div className="image-video">
<img
src={`https://image.tmdb.org/t/p/w500${movieData.image}`}
alt="age"
onClick={() => nav("/player")}
/>
<video
src={op}
autoPlay
loop
muted
onClick={() => nav("/player")}
>
<div className="mute icons flex j-between">
<div className="controls flex">
<IoVolumeHighSharp />
<IoVolumeMuteSharp />
</div>
</div>
</video>
</div>
<div className="info-container flex column">
<h3 className="name" onClick={() => nav("/player")}>
{movieData.name}
</h3>
<div className="icons flex j-between">
<div className="controls flex">
<IoPlayCircleSharp
title="Play"
onClick={() => nav("/player")}
/>
<BsHandThumbsUp />
{isLiked ? (
<BsCheck title="Remove From Fav" />
) : (
<AiOutlinePlus title="Add to Fav" />
)}
</div>
<div className="info">
<BiChevronDown title="More" />
</div>
</div>
<div className="genres flex">
<ul className="flex">
{movieData.genres.map((genre) => (
<li key={genre}>{genre}</li>
))}
</ul>
</div>
</div>
</>
}
</div>
</Container>
);
}
Styled Css
max-width: 16.66666667%;
width: 16.66666667%;
height: 100%;
cursor: pointer;
padding: 0 0.2vw;
position: relative;
&.hovered {
transition-delay: 0.5s;
transform: scale(1.3);
transition: transform 600ms ease-in;
}
img {
position: relative;
border-radius: 0.2rem;
width: 227px;
border-radius: 5px;
max-width: 227px;
height: 100%;
z-index: -1;
}
.hover {
z-index: 999;
height: max-content;
position: absolute;
top: -10vh;
border-radius: 0.3rem;
box-shadow: #000000c4 0px 3px 10px;
width: 250px;
height: 290px;
background-color: #181818;
.image-video {
position: relative;
height: 140px;
img {
width: 227px;
height: 140px;
object-fit: cover;
border-radius: 0.3rem;
top: 0;
z-index: 4;
position: absolute;
}
video {
width: 100%;
height: 140px;
object-fit: cover;
border-radius: 0.3rem;
top: 0;
z-index: 5;
position: absolute;
}
}
.info-container {
padding: 1rem;
gap: 0.5rem;
font-size: 0.8vw;
}
Store your timeoutID in a Ref using React.useRef, the value will then persist across renders
const timeoutRef = useRef(0);
const handleMouseEnter = () => {
timeoutRef.current = setTimeout(() => {
setIsHovered(true);
}, 1000);
};
const handleMouseLeave = () => {
clearTimeout(timeoutRef.current);
setIsHovered(false);
};