Framer-motion: Fire onTap() when click the other button - reactjs

I have RotatingButton which is turning and changing scale when clicked.
(Rotating has to be slower so I used two motions and added duration to rotate.)
Button is adding +1 to state. ( addValue() )
<div>
<div>
<motion.button
onClick={addValue}
animate={{ rotate: rotation }}
transition={{ duration: 0.5, ease: 'easeIn' }}
>
<motion.div whileTap={{ scale: 0.8 }}>
<RotatingButton />
</motion.div>
</motion.button>
</div>
<div>
<button onClick={removeValue}>{text}</button>
</div>
</div>
But the problem is that I have second button which is removing -1 value from state ( removeValue() ) and it also has to turn and scale the RotatingButton.
Rotating is easy, I added dependency:
const [rotation, setRotation] = useState<number>(0);
and setting rotation on each addValue() and removeValue().
setRotation(rotation + 180);
but the problem is that I couldn't find the way to scale RotatingButton for 0.5 sec for example by triggering whileTap() on when removeValue() is triggered.
I've tried to make scale state and use useEffect nad trigger it when rotation is changing:
useEffect(() => {
setScale(0.8);
setTimeout(() => {
setScale(0);
}, 300);
}, [rotation]);
and I've also tried
const controls = useAnimationControls();
with controls.start, controls.mount etc.
To trigger it other way, with animation to change scale for 0.5 sec when changing the state, skipping whileTap.
I've tried onTapStart() too.
None of that worked.
Please help me.

The way was to use sequence
https://www.framer.com/motion/use-animation-controls/#sequence
const controls = useAnimationControls();
const sequence = async () => {
await controls.start({ scale: 0.8, transition: { duration: 0.5 } });
return await controls.start({ scale: 1, transition: { duration: 0.5 } });
};
and change whileTap() to animate={controls}
<motion.button
onClick={addValue}
animate={{ rotate: rotation }}
transition={{ duration: 0.5, ease: 'easeIn' }}
>
<motion.div animate={controls}>
<RotatingButton />
</motion.div>
</motion.button>

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>

React / Framer Motion - why are my child elements not staggered?

I'm building a balance overview within a financial services application. The content is rendered by passing props into a single <BalanceEntry> component and then mapping all available entries into the page.
Using Framer Motion, I want to stagger the entry animation of each rendered <BalanceEntry> as a child by 0.2 seconds.
<div className="pt-16">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, x: 1 }}
transition={{
delay: 0.3,
default: { ease: 'easeInOut' },
staggerChildren: 0.2,
}}
>
{TransactionData.map((item) => {
return (
<div key={item.id}>
<motion.div variants={BalanceEntryVariants}>
<BalanceEntry
transactionDate={item.transactionDate}
transactionType={item.transactionType}
transactionID={item.transactionID}
transactionAmount={item.transactionAmount}
transactionIcon={item.transactionIcon}
/>
</motion.div>
</div>
);
})}
</motion.div>
</div>
);
I have created two motion.divs based on multiple suggestions here on SO to achieve the staggering, but I still fail to have each BalanceEntry component animated with staggering, instead they are always animated as a group:
The variants are setup like this:
const BalanceEntryVariants = {
hidden: {
opacity: 0,
y: -20,
},
visible: {
opacity: 1,
x: 1,
transition: {
ease: 'easeInOut',
},
},
};
Where is my mistake to render each individual BalanceEntry staggered happening?
It seems that this can be solved by defining a variant for the parent motion.div as well, perhaps try something like:
const parent = {
visible: {
opacity: 1,
y: -20,
transition: {
when: 'beforeChildren',
staggerChildren: 0.2,
delay: 0.3,
},
},
hidden: {
opacity: 0,
x: 1,
transition: {
when: 'afterChildren',
},
},
};
Add the variant to parent motion.div:
<motion.div initial="hidden" animate="visible" variants={parent}>
...
</motion.div>
Here is a minimized example running on: stackblitz
There might be other issues as well (not sure about the purpose of y: -20 and x: 1), but hopefully this would still help.

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>
);
}

Framer Motion animation resetting when menu is opened

I have a fancy heading animation using whileInView to trigger, but when I open my menu drawer, the heading animations all reset to the initial state where they are all hidden.
The heading animation function firstly splits up a string into spans containing each word then wraps each letter in a span and nudges the letters down to animate them in one at a time.
It works great, but whenever I click the hamburger, the animated headings all revert to translateY(100%) and won't reveal back even though I have set viewport={{ once: true }}.
Here is my code:
function AnimatedHeading(props) {
const {
text,
stagger = 0.015,
delay = 0.5
} = props || {}
// Word wrapper
const WordWrap = (props) => {
return <span className="whitespace-nowrap">{props.children}</span>;
}
// Breaking string into array of words
const splitWords = text.split(" ");
const words = [];
for (const [, item] of splitWords.entries()) {
words.push(item.split(""));
}
// Add a space ("\u00A0") to the end of each word
words.map((word) => {
return word.push("\u00A0");
});
const variants = {
hidden: {
y: '100%',
transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.85 }
},
animate: {
y: '0%',
transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.75 }
}
};
return (
<AnimatePresence>
<span className="sr-only">{text}</span>
<motion.span
key={`text`}
className="inline-block"
initial="hidden"
whileInView='animate'
viewport={{ once: true }}
transition={{
delayChildren: delay,
staggerChildren: stagger
}}
>
{words.map((word, n) => (
<WordWrap key={n}>
{word.flat().map((char, index) => (
<span key={index} className="overflow-hidden pb-1 inline-block">
<motion.span
key={`animated-heading-${char}-${index}`}
variants={variants}
viewport={{ once: true }}
className="relative inline-block overflow-visible"
>
{char}
</motion.span>
</span>
))}
</WordWrap>
))}
</motion.span>
</AnimatePresence>
)
}
I have tried many different combinations of this, but can't seem to get this bug fixed. Any help appreciated. I'm pretty new to Framer Motion, so I'm sort of learning it as I go.

Animate background using Framer Motion

I'm trying to create a React component where when scrolling down into it, its background transitions from a color to an image.
This is the component:
const TeamSection = () => {
return (
<Background>
<TeamSectionContainer
initial={{
opacity: 0,
}}
whileInView={{
opacity: 1,
}}
transition={{
ease: "easeOut",
duration: 2,
}}
>
<Title>Text</Title>
<Team>Text<Team>
</TeamSectionContainer>
</Background>
);
};
And the styles:
const TeamSectionContainer = styled(motion.div)`
${tw`
bg-no-repeat
w-full
min-h-[200vh]
flex
flex-col
`};
background-image: url(${BackgroundImage});
`;
const Background = tw.div`
bg-[#001F33]
`;
The result is that th animation works for the whole TeamSectionContainer component, indluding its background. I'm trying to make it so that none of the content is animated and only the background is.
How could be this achieved?
I don't think you can animate the opacity of a background image like that. You'd need to add a separate div inside the TeamSectionContainer with the background image and apply the animation to that:
const TeamSection = () => {
return (
<Background>
<TeamSectionContainer>
<motion.div className="backgroundImage"
initial={{
opacity: 0,
}}
whileInView={{
opacity: 1,
}}
transition={{
ease: "easeOut",
duration: 2,
}}
/>
<Title>Text</Title>
<Team>Text<Team>
</TeamSectionContainer>
</Background>
);
};

Resources