Framer motion, calling animation after previous one ended - reactjs

Hello I want to call the animation on line2Array after and only after line1Array has ended his animation. How can I get this effect? I believe I have to do something with orchestratrion but I have no idea how. If you need to clarification I will try to clarify that problem more. But I hope everything is clear.
import { motion } from "framer-motion";
const Landing = () => {
const container = {
hidden: {
opacity: 0,
transition: {
when: "afterChildren",
},
},
visible: {
opacity: 1,
transition: {
when: "beforeChildren",
staggerChildren: 0.35,
},
},
};
const nameAnimation = {
hidden: {
opacity: 0,
y: 100,
},
visible: {
opacity: 1,
y: 0,
},
};
const line1Array = "BLABLABLA".split("");
const line2Array = "HAHAHA".split("");
return (
<motion.div
className="h-screen grid bg-black text-white content-center justify-center text-center"
variants={container}
initial="hidden"
animate="visible"
>
<motion.h1
className="text-[6rem] font-[codeFont]"
variants={container}
initial="hidden"
animate="visible"
>
{line1Array.map((letter, i) => (
<motion.span
className="text-white"
key={letter + i}
variants={nameAnimation}
>
{letter}
</motion.span>
))}
</motion.h1>
<motion.h3
className="text-[6rem] font-[codeFont]"
variants={container}
initial="hidden"
animate="visible"
>
{line2Array.map((letter, i) => (
<motion.span
className="text-white"
key={letter + i}
variants={nameAnimation}
>
{letter}
</motion.span>
))}
</motion.h3>
</motion.div>
);
};
export default Landing;

Related

How to trigger framer motion animation onClick

I am applying a slide in animation using framer motion which slides in the User Card below on page render, but I want to trigger the animation on click of a button
Code below
const wrapperVariants = {
hidden: {
opacity: 0,
x: '100vw'
},
visible: {
opacity: 1,
x: 0,
transition: { type: 'spring', delay: 0.1 }
},
exit: {
x: "-100vh",
transition: { ease: 'easeInOut' }
}
};
<motion.div
variants={wrapperVariants}
initial="hidden"
animate="visible"
exit="exit"
>
<UserCard />
</motion.div>
<button onClick={() => {
I want to Trigger the Animation on button click not just on render
}}></button>
You can use animation control hook in framer motion and for your case it would be sth like this :
const wrapperVariants = {
hidden: {
opacity: 0,
x: '100vw',
},
visible: {
opacity: 1,
x: 0,
transition: { type: 'spring', delay: 0.1 },
},
exit: {
x: '-100vh',
transition: { ease: 'easeInOut' },
},
};
const controls = useAnimationControls();
<motion.div variants={wrapperVariants} initial="hidden" animate={controls} exit="exit">
<UserCard />
</motion.div>
<button onClick={() => controls.start('visible')}></button>
You could do something like
const [clicked, setClicked] = useState(false)
<motion.div
variants={wrapperVariants}
initial="hidden"
animate= {clicked ? 'visible' : 'hidden'}
exit="exit"
>
<button onClick={() => setClicked(!clicked)}></button>

How can I stop all the videos in the array playing at the start?

So I am using React Player. I have set up a toggle button so that I can play and pause the video. This all works fine. What I would like to do is have the first video from an array of three videos play as soon as the page loads. The problem I am having is that all the videos play. Even though you can only actually see the first video playing, if I hit pause then I can hear the audio from the other videos. What should I do to fix this?
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { wrap } from "popmotion";
import ReactPlayer from "react-player";
export default function VideoSlider(props) {
const { videos } = props;
const [[page, direction], setPage] = useState([0, 0]);
const videoIndex = wrap(0, videos.length, page);
const paginate = (newDirection) => {
setPage([page + newDirection, newDirection]);
};
let timeout;
const handleClick = (direction) => {
paginate(direction);
};
console.log("VIDEO", videos[videoIndex]);
const [playing, setPlaying] = React.useState(true);
const play = () => setPlaying(true);
const pause = () => setPlaying(false);
return (
<motion.div className="videpSliderWrapper">
<AnimatePresence initial={false} custom={direction}>
<ReactPlayer
playing={playing}
onPlay={play}
onPause={pause}
className="videoSlider"
key={page}
url={videos[videoIndex]}
custom={direction}
initial={{
opacity: 0,
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
animate={{
opacity: 1,
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
exit={{
opacity: 0,
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
width="100%"
height="auto"
></ReactPlayer>
</AnimatePresence>
<motion.div
className="blankIcon"
initial={{ opacity: 0 }}
whileHover={{ opacity: 1 }}
whileTap={{ opacity: 1 }}
onClick={() => setPlaying(!playing)}
>
{playing ? (
<div className="pauseIcon"></div>
) : (
<div className="playIcon"></div>
)}
</motion.div>
<motion.div
className="next"
whileHover={{
backgroundColor: "#ffffff",
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
onClick={() => handleClick(1)}
></motion.div>
<motion.div
className="prev"
whileHover={{
backgroundColor: "#ffffff",
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
onClick={() => handleClick(-1)}
></motion.div>
</motion.div>
);
}

react framer-motion "AnimatePresence" animation on exiting, trying to get it working..noob framer-motion user

I have all my animations working good, except when I when I click an item. No animation out. Card item animates in, but no animation out. I'm trying to implement AnimatePresence, have to admit I've rewritten this left from Sunday and back again, and tried to follow a few tuts on it, but I cant seem to grasp it. I have my route animations in and out working with AnimateSharedLayout.
Here be the code across three pages trying to have transition animation between items listed to item selected.
const containerVariants = {
initial: {
opacity: 0,
y: '100vw',
scale: 2,
},
in: {
opacity: 1,
y: 0,
scale: 1,
},
out: {
opacity: 0,
y: '-100vw',
scale: 0,
},
};
const containerTransitions = {
type: 'tween',
ease: 'anticipate',
duration: 2,
};
const containerVariants1 = {
initial: {
opacity: 0,
x: '100vw',
scale: 0,
},
in: {
opacity: 1,
x: 0,
scale: 1,
},
out: {
opacity: 0,
x: '-100vw',
scale: 0,
},
};
const containerTransitions1 = {
type: 'tween',
ease: 'anticipate',
duration: 3,
};
const LGallery = () => {
return (
<>
<AnimatePresence key={Items._id}>
<motion.div
initial='initial'
animate='in'
key={Items._id}
exit='out'
variants={containerVariants}
transition={containerTransitions}
className='container'
>
<h1>filler title</h1>
<Row
as={motion.div}
key={Items._id}
initial='initial'
animate='in'
exit='out'
variants={containerVariants1}
transition={containerTransitions1}
className='card-list '
>
{Items.map((Items) => (
<Col key={Items._id}>
<WorkItems Items={Items} />
</Col>
))}
</Row>
</motion.div>
</AnimatePresence>
</>
);
};
export default LGallery;
list of items page
const containerVariants = {
initial: {
opacity: 0,
x: '-50vw',
y: '10vw',
scale: 0,
},
in: {
opacity: 1,
x: 0,
y: 0,
scale: 1,
},
out: {
opacity: 0,
x: '10vw',
y: '-50vw',
scale: 0,
},
};
const containerTransitions = {
type: 'tween',
ease: 'anticipate',
duration: 4,
};
const containerVariants1 = {
initial: {
opacity: 0,
y: '60vw',
scale: 0,
},
in: {
opacity: 1,
y: 0,
scale: 1,
},
out: {
opacity: 0,
y: '-60vw',
scale: 0,
},
};
const containerTransitions1 = {
type: 'tween',
ease: 'anticipate',
duration: 5,
};
const containerVariants2 = {
initial: {
opacity: 0,
y: '-60vw',
scale: 0,
},
in: {
opacity: 1,
y: 0,
scale: 1,
},
out: {
opacity: 0,
y: '60vw',
scale: 0,
},
};
const containerTransitions2 = {
type: 'tween',
ease: 'anticipate',
duration: 5,
};
const WorkItems = ({ Items }) => {
return (
<>
<AnimatePresence key={Items._id}>
<Link to={`/work/${Items._id}`}>
<Card
as={motion.div}
initial='initial'
animate='in'
exit='out'
variants={containerVariants}
transition={containerTransitions}
className='card-content-container'
>
<Link to={`/work/${Items._id}`}>
<Card.Img
className='card-image-container'
src={Items.image}
// variant='top'
/>
</Link>
<Card.Body
as={motion.div}
key={Items._id}
initial='initial'
animate='in'
exit='out'
variants={containerVariants1}
transition={containerTransitions1}
className='card-content'
>
<Card.Title
as={motion.div}
key={Items._id}
initial='initial'
animate='in'
exit='out'
variants={containerVariants2}
transition={containerTransitions2}
>
<strong>{Items.title}</strong>
</Card.Title>
<Card.Text>
<motion.div
key={Items._id}
initial='initial'
animate='in'
exit='out'
variants={containerVariants2}
transition={containerTransitions2}
className='card-content'
>
{Items.description}
</motion.div>
</Card.Text>
</Card.Body>
</Card>
</Link>
</AnimatePresence>
</>
);
};
export default WorkItems;
individual item page
const containerVariants = {
initial: {
opacity: 0,
y: '-80vw',
scale: 0,
},
in: {
opacity: 1,
y: 0,
scale: 1,
},
out: {
opacity: 0,
y: '80vw',
scale: 0,
},
};
const containerTransitions = {
type: 'tween',
ease: 'anticipate',
duration: 2,
};
const unItems = ({ match }) => {
const item = Items.find((p) => p._id === match.params.id);
return (
<>
<AnimatePresence>
<motion.div
initial='initial'
animate='in'
exit='out'
variants={containerVariants}
transition={containerTransitions}
className='portItem'
>
<Image
className='image-container'
src={item.image}
alt={item.title}
/>
<motion.div
initial='initial'
animate='in'
exit='out'
variants={containerVariants}
transition={containerTransitions}
className='portItem'
>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{item.title}</h3>
</ListGroup.Item>
<ListGroup.Item>
<h3>{item.blurb}</h3>
</ListGroup.Item>
<ListGroup.Item>
<h3>{item.description}</h3>
</ListGroup.Item>
</ListGroup>
</motion.div>
<motion.div
initial='initial'
animate='in'
exit='out'
variants={containerVariants}
transition={containerTransitions}
className='portItem'
>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{item.gitLink}</h3>
</ListGroup.Item>
<ListGroup.Item>
<h3>{item.hostLink}</h3>
<h3>{item.phoneQR}</h3>
</ListGroup.Item>
</ListGroup>
</motion.div>
<motion.div
initial='initial'
animate='in'
exit='out'
variants={containerVariants}
transition={containerTransitions}
className='portItem'
>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{item.bIcon}</h3>
</ListGroup.Item>
</ListGroup>
<Link to='/work'>
<NButton>Go Back</NButton>
</Link>
</motion.div>
</motion.div>
</AnimatePresence>
</>
);
};
export default unItems;
The AnimatePresence tags need to wrap the element that will be removed from the DOM. You can’t have the tags inside the conditionally rendered component, which looks like what you have here. In that case the AnimatePresence tag gets removed from the page with the component and it never has a chance to run the exit animation.
So you most likely just need to move the tags into the parent component.
Also, make sure all of the components you’re trying to animate with AnimatePresence have a unique key prop. It looks like you’re missing that in some of these examples.

StaggerChildren is not working with Framer Motion

When my app loads initially all the list items are animating together instead of the staggering effect. I am also using AnimatePresence for exit animations when my todo is deleted from state.
For Framer Motion I am using below variants
const listItemContainerVariant = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 1 }
}
};
const listItemVariant = {
hidden: { x: -500 },
show: { x: 0, transition: { type: "spring", stiffness: 120 } },
exit: { scale: 1.1, x: 500, opacity: 0 },
hover: { scale: 1.1 }
};
I am using useState for managing my todos:
const [todos, setTodos] = useState(
localStorage.todos ? JSON.parse(localStorage.getItem("todos")) : []
);
Below is my JSX Code
<motion.ul
id="todo-list"
variants={listItemContainerVariant}
initial="hidden"
animate="show"
>
<AnimatePresence>
{todos.map((todo, index) => (
<motion.li
variants={listItemVariant}
initial="hidden"
animate="show"
exit={"exit"}
whileHover="hover"
key={todo.id}
className="list-item"
>
<span className="list-item-text">{todo.text}</span>
<button onClick={() => handleEdit(todo)} className="edit-btn">
<span role="img" aria-label="emoji">
✏️
</span>
</button>
<button
onClick={() => handleDelete(todo.id)}
className="delete-btn"
>
<span role="img" aria-label="emoji">
🗑
</span>
</button>
</motion.li>
))}
</AnimatePresence>
</motion.ul>
I figured out that for staggerChildren to work I had to take off the exit and hover properties from listItemVariant and add it separately in the motion.li component. This little fix got it working.
const listItemVariant = {
hidden: { x: -500 },
show: { x: 0, transition: { type: "spring", stiffness: 120 } }
};
<motion.li variants={listItemVariant}
exit={{ scale: 1.1, x: 500, opacity: 0 }}
whileHover={{
scale: 1.1
}}
key={todo.id}
className="list-item"
>

Exit animation for children items not working in Framer Motion

I am making a hamburger menu animation in React using Framer Motion. When I click on the hamburger menu, the side drawer and the navigation items slide in from the left.
When I click on the close menu icon, then the whole side drawer slides out to the left(which means the exit prop on SideDrawer component is working).
What do I want?
When I click on the close icon, I want the navigation items to slide out first and then the side drawer. I have tried adding exit prop to children navigation items. But it does not work.
How can I achieve the desired effect?
Code snippets are as below:
src/App.js
import React, { useState } from "react";
import "./App.css";
import Menu from "./components/Menu";
import SideDrawer from "./components/SideDrawer";
import Overlay from "./components/Overlay";
const App = () => {
const [menuOpen, setMenuOpen] = useState(false);
const handleMenuClick = () => {
setMenuOpen(!menuOpen);
};
return (
<div className="App">
<Menu menuOpen={menuOpen} onMenuClick={handleMenuClick} />
<SideDrawer menuOpen={menuOpen} />
<Overlay menuOpen={menuOpen} />
</div>
);
};
export default App;
src/components/Menu.js
import React, { useState } from "react";
import { motion } from "framer-motion";
const lineOneVariants = {
initial: { rotate: "0deg" },
animate: { y: ".8rem", rotate: "45deg", transformOrigin: "center center" },
};
const lineTwoVariants = {
initial: { opacity: 1 },
animate: { opacity: 0 },
};
const lineThreeVariants = {
initial: { rotate: "0deg" },
animate: { y: "-.8rem", rotate: "-45deg", transformOrigin: "center center" },
};
const Menu = ({ onMenuClick, menuOpen }) => {
return (
<div className="hamburger_menu">
<div className="hamburger_menu-line-container" onClick={onMenuClick}>
<motion.div
variants={lineOneVariants}
initial="initial"
animate={menuOpen ? "animate" : "initial"}
className="hamburger_menu-line-1"
></motion.div>
<motion.div
variants={lineTwoVariants}
initial="initial"
animate={menuOpen ? "animate" : "initial"}
className="hamburger_menu-line-2"
></motion.div>
<motion.div
variants={lineThreeVariants}
initial="initial"
animate={menuOpen ? "animate" : "initial"}
className="hamburger_menu-line-3"
></motion.div>
</div>
</div>
);
};
export default Menu;
src/components/Overlay.js
import React from "react";
import { motion } from "framer-motion";
const overlayVariants = {
initial: { opacity: 0 },
animate: { opacity: 1 },
};
const Overlay = ({ menuOpen }) => {
return (
<motion.div
variants={overlayVariants}
initial="initial"
animate={menuOpen ? "animate" : "initial"}
className="overlay"
></motion.div>
);
};
export default Overlay;
src/components/SideDrawer.js
import React from "react";
import { motion, AnimatePresence } from "framer-motion";
const drawerVariants = {
initial: {
x: "-100vw",
opacity: 0,
},
animate: {
x: 0,
opacity: 1,
transition: {
type: "linear",
ease: "easeInOut",
staggerChildren: 0.1,
delayChildren: 0.15,
},
},
};
const drawerMenuVariants = {
initial: { x: "-5rem", opacity: 0 },
animate: {
x: 0,
opacity: 1,
transition: {
type: "linear",
ease: "easeInOut",
},
},
};
const SideDrawer = ({ menuOpen }) => {
return (
<AnimatePresence>
{menuOpen && (
<motion.div
variants={drawerVariants}
initial="initial"
animate="animate"
exit="initial"
className="sideDrawer"
>
<nav className="sideDrawer-menu">
<ul>
<motion.li
variants={drawerMenuVariants}
whileHover={{ scale: 1.05 }}
>
Register/Login
</motion.li>
<motion.li
variants={drawerMenuVariants}
whileHover={{ scale: 1.05 }}
>
About
</motion.li>
<motion.li
variants={drawerMenuVariants}
whileHover={{ scale: 1.05 }}
>
Projects
</motion.li>
<motion.li
variants={drawerMenuVariants}
whileHover={{ scale: 1.05 }}
>
CV
</motion.li>
</ul>
</nav>
</motion.div>
)}
</AnimatePresence>
);
};
export default SideDrawer;
You need to tell the side drawer to wait until the exit animation for the children finishes before starting its own exit animation.
You can do this by using the when property of the transition. See Orchestration in the docs.
In your case, you'd add it to the initial variant of your drawerVariants, since that's the variant you animate to on exit:
initial: {
x: "-100vw",
opacity: 0,
transition: {
when: "afterChildren"
}
},
You probably also want to add some staggering and easing there if you want to mirror the animate in behavior, but I'll leave that up to you.

Resources