framer-motion

Reveal Card Animation with Framer Motion


I want to achieve the animation in the following link: https://www.hover.dev/components/cards#reveal-cards

But I'm using framer motion. I was able to recreate it, but the exit animation jitters some how, it's not smooth as in the example.

I tried using plain css, but it didn't yield the needed result either.

Any help is appreciated.

Here's my code:

const PortfolioSection: FC = () => {

    const list = [
        {
            title: "abc",
            url: "https://www.example.com",
            image: "https://images.unsplash.com/photo-1488972685288-c3fd157d7c7a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80"
        },
        {
            title: "klm",
            url: "https://www.example.com",
            image: "https://images.unsplash.com/photo-1487958449943-2429e8be8625?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80"
        },
        {
            title: "ost",
            url: "https://www.example.com",
            image: "https://images.unsplash.com/photo-1449157291145-7efd050a4d0e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80"
        },
        {
            title: "xyz",
            url: "https://www.example.com",
            image: "https://images.unsplash.com/photo-1598818384697-62330d600309?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80"
        }
    ];

    const imgMotion = {
        initial: {
            top: "0%",
            right: "0%",
            scale: 1
        },
        hover: {
            top: "25%",
            right: "25%",
            scale: 0.5,
            transition: {
                duration: 0.3,
                // type: "tween",
                // ease: "easeIn",
                all: { type: "tween", damping: 5, stiffness: 500 }
            }
        }
    };


    return (
        <HomePageSection>
            <Grid
                container
                rowSpacing={4}
                columnSpacing={2}
                justifyContent="center"
                sx={{ height: "100%" }}
            >
                {list.map((item, i) => (
                    <Grid
                        key={i}
                        size={{ md: 6, xs: 12 }}
                    >
                        <motion.div
                            initial="initial"
                            // animate="initial"
                            whileHover="hover"
                            whileTap="hover"
                            exit="exit"
                            style={{
                                width: "100%",
                                height: "30vh",
                                position: "relative",
                                border: "4px solid green"
                            }}
                        >
                            <div
                                style={{
                                    height: "50%",
                                    display: "grid",
                                    placeContent: "center",
                                    textTransform: "uppercase",
                                    backgroundColor: "black",
                                    color: "white"
                                }}
                            >
                                <Typography variant="h4">
                                    { item.title }
                                </Typography>
                            </div>
                            <motion.div
                                variants={imgMotion}
                                style={{
                                    height: "100%",
                                    width: "100%",
                                    backgroundImage: `url(${item.image})`,
                                    backgroundSize: "cover",
                                    backgroundPosition: "center center",
                                    zIndex: "10",
                                    position: "absolute"
                                }}
                            />
                            <Link
                                href={item.url}
                                target="_blank"
                                style={{
                                    position: "absolute",
                                    bottom: 0,
                                    right: 0,
                                    height: "50%",
                                    width: "50%",
                                    display: "grid",
                                    placeContent: "center",
                                    backgroundColor: "black"
                                }}
                            >
                                Read more
                            </Link>
                        </motion.div>
                    </Grid>
                ))}
            </Grid>
        </HomePageSection>
    );
};

Solution

  • The reason your exit animation could be glitchy is because you don't have an exit variant defined in your imgMotion. However, you don't need the exit variant, as that is only really useful when using AnimatePresence. You're implementation is close, but I changed a couple things to make it work a bit better. You can find a working example here (I also recreated the effect with pure CSS and TailwindCSS). See the fix in the comments in the code below:

    // Instead of animating the left and right properties,
    // animate the width and height. This is much
    // simpler and seems to perform better
    
    const imgMotion: Variants = {
      initial: {
        width: '100%',
        height: '100%',
      },
      hover: {
        width: '50%',
        height: '50%',
        transition: {
          duration: 0.3,
          // type: "tween",
          // ease: "easeIn",
          all: { type: 'tween', damping: 5, stiffness: 500 },
        },
      },
    };
    
    ...
    
    <motion.div
      initial="initial"
      whileHover="hover"
      whileTap="hover"
      // Remove the exit variant, it's unnecessary and
      // only really works with AnimatePresence
      style={{
           ...existing styles
      }}
    >
      <div
        style={{
           ...existing styles
        }}
      >
        <h4>{item.title}</h4>
      </div>
      <motion.div
        variants={imgMotion}
        style={{
           ...existing styles,
          // Set the image to be anchored to the bottom left of the card
          bottom: 0,
          left: 0,
        }}
       />
       <a
         href={item.url}
         target="_blank"
         style={{
           ...existing styles
         }}
       >
         Read more
       </a>
     </motion.div>