skip to Main Content

i would like to create a framer motion animation in React that removes and element on click and visualizes another element.

Here is an example
example

This is my code:

            {!tileOpen ? (
              <motion.div
                whileHover={{ scale: 1.2 }}
                onClick={handleTrigger}
                className={styles.tile}
            
              >
              </motion.div>
            ) : (
              <motion.div
                animate={{ opacity: 1, scale: 1 }}
                transition={{ delay: 0.9, duration: 0.5 }}
                onClick={handleTrigger}
                className={styles.tileOpen}
              >
               <Image
                  src={`/test/test.svg`}
                  alt="testimage"
                  width={116}
                  height={35}
                  
                />
              </motion.div>
            )}

The problem is that in my animation the element does not appear the same way. I am not able to delay the animation.

2

Answers


  1. 

The issue you are facing is the sum of states and framer-motion.
    You can’t swap a component and animate it in the same time.

    You could try to play the handleTrigger after the animation is done.

    Then your logic should look like this:

    const [tileOpen, setTileOpen] = useState(false);
        const [animatingOut, setAnimatingOut] = useState(false);
        
        const handleTrigger = () => {
            if (!tileOpen) {
                setAnimatingOut(true);
                setTimeout(() => {
                setTileOpen(true);
                setAnimatingOut(false);
            }, 500);
            } else {
                setAnimatingOut(true);
                setTimeout(() => {
                setTileOpen(false);
                setAnimatingOut(false);
            }, 500);
        }
    };
    

    this matches the exit duration of your animation (500ms)

    And now you can make sure the title isn’t swapped until the animation is done:

    {!tileOpen && !animatingOut && (
        <motion.div
                        key="closedTile"
                        whileHover={{ scale: 1.2 }}
                        onClick={handleTrigger}
                        className={styles.tile}
                        initial={{ opacity: 1, scale: 1 }}
                        animate={{ opacity: 1, scale: 1 }}
                        exit={{ opacity: 0, scale: 0.8 }}
                        transition={{ duration: 0.5 }}
                      >
                        {/* Content for the closed tile */}
                      </motion.div>
                    )}
                    {tileOpen && !animatingOut && (
                      <motion.div
                        key="openTile"
                        onClick={handleTrigger}
                        className={styles.tileOpen}
                        initial={{ opacity: 0, scale: 0.8 }}
                        animate={{ opacity: 1, scale: 1 }}
                        exit={{ opacity: 0, scale: 1.2 }}
                        transition={{ duration: 0.5 }}
                      >
                        <Image
                          src={`/test/test.svg`}
                          alt="testimage"
                          width={116}
                          height={35}
                        />
        </motion.div>
    )}
    

    I hope this works for you. I use the same technique and for me this is the one which works.

    Login or Signup to reply.
  2. Is this what you want?

    Demo

    import React, { useState } from "react";
    import { motion, AnimatePresence } from "framer-motion";
    import "./styles.css";
    
    const CardReveal: React.FC = () => {
      const [isRevealed, setIsRevealed] = useState(false);
    
      const handleToggle = () => {
        setIsRevealed((prev) => !prev);
      };
    
      return (
        <div className="card-container">
          <motion.div
            onClick={handleToggle}
            className="image-container"
            initial={{ opacity: 0 }}
            animate={{ opacity: isRevealed ? 1 : 0 }}
            transition={{ duration: 0.5 }}
          >
            <motion.img
              src="https://i.imgur.com/ApagROY.png"
              alt="Background"
              className="revealed-image"
              initial={{ scale: 0.9 }}
              animate={{ scale: isRevealed ? 1 : 0.9 }}
              transition={{ duration: 0.5 }}
            />
          </motion.div>
          {!isRevealed && (
            <motion.div
              onClick={handleToggle}
              className="card"
              initial={{ opacity: 1, scale: 1 }}
              animate={{ scale: isRevealed ? 0.9 : 1 }}
              exit={{ opacity: 0, scale: 0.5, transition: { duration: 0.125 } }}
              whileHover={{ scale: 1.1 }}
              whileTap={{ scale: 0.95 }}
            >
              <motion.div
                initial={{ scale: 1 }}
                whileHover={{ scale: 1.1 }}
                whileTap={{ scale: 0.95 }}
              ></motion.div>
            </motion.div>
          )}
        </div>
      );
    };
    
    export default CardReveal;
    
    
    body {
      background-color: #0d202d;
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
    }
    .card-container {
      position: relative;
      border-radius: 15px;
      overflow: hidden;
      width: 200px;
      height: 200px;
    }
    
    .image-container {
      width: 100%;
      height: 100%;
      background-color: #051523;
      position: absolute;
      top: 0;
      left: 0;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    .revealed-image {
      width: 90%;
      height: 90%;
      object-fit: cover;
    }
    
    .card {
      width: 100%;
      height: 100%;
      background-color: #2f4452;
      display: flex;
      justify-content: center;
      align-items: center;
      color: #fff;
      position: absolute;
      top: 0;
      left: 0;
      cursor: pointer;
    }
    
    .toggle-button {
      position: absolute;
      bottom: 10px;
      right: 10px;
      background: #fff;
      border: none;
      padding: 5px 10px;
      cursor: pointer;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search