How to perform a Reverse Animation Using Framer Motion - reactjs

I have start doing animations in web recently using Framer Motion. I was wondering how can I make it work in reverse. In my case, I have a Modal which animates from positive X-aixs to 0 Position on X-axis, when animation is completed.
I want it go off the screen in a same way as it entered the screen.
Here's the code
const variants = {
hidden: { x: 400, transition: { duration: 0.3 } },
visible: { x: 0, transition: { duration: 0.3 } },
};
return(
<motion.section
animate='visible'
initial='hidden'
variants={variants}
className='container'
>...
</motion.section>
)
here is the link to the codesandbox

Related

How to properly animate group of mapped items with framer-motion while array is changing?

I have an array like:
base = [{title:'foo',items: [1,2]},{title:'bar',items:[1,2,3,4]}], then I map through this array with conditions:
<motion.div variants={container} initial="hidden" animate="visible" exit={'hidden'}>
{base[current].items.map(i =>
<motion.div key={index} variants={item} >
<div{i}</div>
</motion.div>)}
</motion.div>
I need to animate all children, but now I only animate that children, which more in array...
for example: (in that case) two children from first array in base is visible, if i change array (by toggle with a button for example) children 3 and 4 are appearing with animation, and hiding too, but children 1 and 2 changing without animation, how to resolve it ?
animation variants:
export const container = {
hidden: { opacity: 1, scale: 0 },
visible: {
opacity: 1,
scale: 1,
transition: {
delayChildren: 0.3,
staggerChildren: 0.2
}
}
};
export const item = {
hidden: { y: 20, opacity: 0 },
visible: {
y: 0,
opacity: 1
}
};
To resolve it we must to wrap our motion.div with AnimatePresence with attributes initial={false} mode={"wait"}, where mapped items will have different keys: for example key={i.title}

Inconsistent delay time for executing code

I'm trying to write some code to play animations with the help of the react package framer-motion:
import React from "react";
import './Line.css';
import { motion, useAnimation } from 'framer-motion';
export const Line = ({speed}) => {
const [goingRight, setGoingRight] = React.useState(false);
const [delay, setDelay] = React.useState(1);
const [oldTime, setOldTime] = React.useState(new Date().getTime());
const controls = useAnimation()
React.useEffect(() => {
controls.start({
x: 280,
transition: { delay: 0, duration: speed, type: 'tween', ease: 'linear' }
})
}, [])
const start = async () => {
setDelay((new Date().getTime()) - oldTime);
setOldTime(new Date().getTime());
await setGoingRight(!goingRight);
if (goingRight) {
await controls.start({
x: 280,
transition: { delay: 0, duration: speed, type: 'tween', ease: 'linear' }
})
} else {
await controls.start({
x: 0,
transition: { delay: 0, duration: speed, type: 'tween', ease: 'linear' }
})
}
}
return (
<>
{delay}
<div className='outer'>
<motion.div className='ball'
animate={controls}
onAnimationComplete={() => start()}
/>
<div className='line'/>
</div>
</>
)
}
This component renders a horizontal line and a circle that sits on that line, the circle starts at the left side of the line and is animated to the right. After that, another animation is made to move the circle back to the left and then the cycle continues.
Each animation takes as long as the speed property (in seconds).
I'm also displaying the delay state variable which does the appropriate math to so determine how many milliseconds the most recent animation took since the last animation.
Here's my problem, let's say I render this component with a speed property of 1. In a perfect world, delay would always = 1000, but of course, there's some time loss for executing the code in the start() function so delay usually = 1017 instead. That would be fine because I could account for the difference, but strangely, delay is sometimes exactly 1000 instead (around 1 in every 4 animations it seems, but it's not consistent).
I need these animation times to be precise and this is too much of a deviation for what I need and I can't seem to figure out how to fix it. I have a suspicion this might be just be the nature of doing all this in real time and that it may be an unsolvable problem, but I figured I'd ask in case anyone has found themselves in a scenario like this before.
Instead of using multiple animation that are run after another, I've tried just using one infinitely repeating animation like this:
controls.start({
x: 280,
transition: { delay: 0, duration: speed, type: 'tween', ease: 'linear' repeat: Infinity, repeatType: 'reverse', }
})
The problem with doing this is that I need code to run after the circle hits the end of the line every time, which before I was using onAnimationComplete to accomplish, but onAnimationComplete seems to only be triggered when the entire animation completes, not also when it repeats

whileInView and whileHover conflicting

I have a grid component and a card component that goes inside this. I wanted to animate each child of the grid whileInView so I added this prop to the parent and was working properly.
The problem comes when I try to add whileHover prop to the childs, it causes the whileInView prop to be disabled. I have also tried to add it on the parent but it makes the grid to anime as a whole.
Grid component:
<SmallGridSection key={`div-${title}`}>
<SmallGrid
key={`grid-${title}`}
variants={gridVariants}
initial={"hidden"}
whileInView={"visible"}
viewport={{ once: true }}
>
{array.map(
technology =>
<Card technology={technology} key={technology.name}/>
)}
</SmallGrid>
</SmallGridSection>
using this variants:
const gridVariants = {
visible: {
transition: {
when: "beforeChildren",
delayChildren: 1,
staggerChildren: 0.1,
},
},
hidden: {
transition: {
when: "afterChildren",
duration: 1,
type: "spring",
},
},
}
Card component:
const Card = ({ technology, ...props }) => {
return(
<CardStyled
color={technology.color}
variants={cardVariants}
title={technology.name}
( *this is where i tried to add "whileHover={"hovered"}*)
{...props}
>
<a>
<TechnologyIcon technology={technology}/>
</a>
</CardStyled>
)
}
with this variants:
const cardVariants = {
hidden: {
opacity: 0,
rotate: "100deg"
},
visible: {
opacity: 1,
rotate: 0,
},
animated: {
translateY: "-5px",
transition: {
type: "spring",
delay: 3,
duration: 0.5,
repeat: "infinity",
},
},
hovered: {
scale: 1.08,
transition: {
duration: 0.2,
ease: "easeInOut",
},
}
}
I stumbled upon this problem as well and managed to solve it by applying whileInView and whileHover to different levels, not to one element. So you need to wrap your card content into another div (or whatever you want) and apply whileHover to it as well as variants with the hover property (actually, the whole cardVariants will work as well):
<CardStyled
color={technology.color}
variants={cardVariants}
title={technology.name}
>
<motion.div
whileHover="hovered"
variants={{
hover: cardVariants.hovered,
}}
>
<a>
<TechnologyIcon technology={technology}/>
</a>
</motion.div>
</CardStyled>
Of course, it would make sense to move the new div into your CardStyled component, but the overall structure should look this way.

Why y and x axis parameter didn't work for staggerChildren animation in framer motion?

My question is fairly simple but i'm struggling to find the solution. Here i'm trying to create a staggering animation for each letter in a sentence, i want to use the y axis as a parameter to animate but i'm not getting the result i wanted as the sentence fully mounted without animating. But when i tried to use opacity as a parameter, it works exactly fine. What did i do wrong here? Any help would be appreciated :)
// import "./styles.css";
import { motion } from "framer-motion";
export default function App() {
const quotes = "Hello World.";
const parent = {
animate: {
transition: {
staggerChildren: 0.1,
},
},
};
const child = {
initial: { y: 400 },
animate: {
y: 0,
},
};
return (
<div>
<motion.div variants={parent} initial='initial' animate='animate'>
{
quotes.split('').map((item, index) => (
<motion.span variants={child} key={index}>{item}</motion.span>
))
}
</motion.div>
</div>
);
}
To help you see my problem, here is codesandbox example
https://codesandbox.io/s/busy-mountain-fv58xc?file=/src/App.js:0-620
Animating x and y doesn't work for <span> because it's and inline element. It flows with the content and doesn't have an explicit x and y position to animate.
You can change your spans to a block-level element (like div), or you could add some styling to tell the spans to display as blocks:
<motion.span style={{display: "inline-block"}} variants={child} key={index}>
{item}
</motion.span>

framer-motion custom hover variant is affected by a delay from another variant

I have a set of variants defined as such:
const letterVariants: Variants = {
visible: (custom: number) => ({
opacity: 1,
y: 0,
transition: {
delay: custom * 0.1,
},
}),
hidden: {
opacity: 0,
y: 100,
},
hover: {
y: -30,
transition: {
delay: 0,
},
},
};
which are used for the hero title:
<h1 className="font-peralta">
{letters.map((letter, i) => (
<motion.div
className="inline-block"
variants={letterVariants}
custom={i}
initial="hidden"
animate="visible"
whileHover="hover"
key={nanoid()}
>
{letter}
</motion.div>
))}
</h1>
On initial render, each letter of the hero title fades in from the bottom. I am trying to make it also that when I hover (before or after animation end), the letters slide up a bit. The problem I am facing is that the custom transition delay that allows for the letters to pop up one-by-one is also applied to the hover variant. So when I hover over the first letter, it moves up and down immediately, if I hover over the last letter, it goes up but takes x * 0.1 seconds to return to its normal position.
It's almost exactly the same as in this post except that solution has not worked for me.

Resources