Search code examples
htmlcssreactjsmern

Creating a hovering effect on div after 1 sec to scale it and unscale when the user leaves


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;
    }

Solution

  • 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);
      };