motiondivision / motion

A modern animation library for React and JavaScript
https://motion.dev
MIT License
24.87k stars 827 forks source link

[BUG/QUESTION] separating mount animation from other animation? #634

Closed sbtly closed 3 years ago

sbtly commented 4 years ago

https://codesandbox.io/embed/loving-wildflower-elkiw?fontsize=14&hidenavigation=1&theme=dark Edit loving-wildflower-elkiw

I'm trying to do this thing:

  1. When div mounts, animate x to 100, with duration: 5.
  2. After div has mounted, if value is false, animate x to 300 with duration: 0.1.

I tried to combine two variants with animate={["animate", value ? "true" : "false"]}, variants={{ ...mountVariants, ...valueVariants }}, but it seems that animate variant gets overridden by false variant.

I know that if I do something with setValue logic, or different variant values it may work in this example, but my actual project code is much complicated than this. I can't tweak like that.

I really need to know how to separate mount animation from other animation. My ideal would be enter prop like below, but there isn't.

initial="initial"
enter="enter"
animate={value ? "true" : "false"}
exit="exit"

Is there a way?

alexis-regnaud commented 4 years ago

What was the solution for that one ? I need also to have a seperate animation between the first render and the others interactions/animations after

mdijoux commented 3 years ago

I had the same issue. I'm trying to have a different delay for the first animation and the one which occur with a whileHover. I think the best way to handle this would be to have VariantLabel for the previous variant in the variant function instead of relying on a value.

Here is the workaround

import React from "react"
import { motion } from "framer-motion"
import styled from "styled-components";

const Targets = {
  HIDDEN: "hidden",
  VISIBLE: "visible",
  HOVER: "hover",
}

const COLUMNS = 4;

const item = {
  hidden: {
    target: Targets.HIDDEN,
    scale: 0.8,
    opacity: 0,
  },
  visible: (index, from) => {
    const col = index % COLUMNS + 1;
    const row = Math.floor(index / COLUMNS) + 1;

    const d = col + row;

    return {
      target: Targets.VISIBLE,
      scale: 1,
      opacity: 1,
      transition: {
        delay: from.target === Targets.HOVER ? 0 : d * 0.2
      }
    }
  },
  hover: {
    target: Targets.HOVER,
    scale: 1.02
  }
};

  const colors = {
    pink: "#e41779"
  }

const Image = styled.img`
  object-fit: cover;
  width: 300px;
  height: 400px;
  border-radius: 5px;
  box-shadow: 0 0 0 4px transparent;
  transition: all 0.3s cubic-bezier(.25,.8,.25,1);

  &:hover {
    box-shadow: 0 0 0 4px ${colors.pink};
    cursor: pointer;
  }
`

const PhotoGrid = ({photos, style, ...rest}) => {

    return (
      <div 
          {...rest}
      >
          {photos.map((photo, index) => {
              return <Image custom={index} initial="hidden" animate="visible" variants={item} whileHover="hover" as={motion.img} loading="lazy"  src={photo.url}  key={photo.url}/>
          })}
      </div>
    )
}

export default styled(PhotoGrid)`
  display: grid;
  grid-template-columns: repeat(${COLUMNS}, 300px);
  grid-gap: 30px;
  justify-content: center;
  padding: 30px;
`;
mattgperry commented 3 years ago

You can set a variant via useState() and pass it to animate, first one that animates x:100, and then via the onAnimationComplete handler set it to the second variant