'react The swipe animation will be performed twice
When you press the Circle button, the Box moves to the right and disappears from the screen.
Additional information (FW/tool version, etc.)
react
scss
Typescript
framer-motion
import "./style.scss";
import React, { FunctionComponent, useState } from "react";
import { useMotionValue, useTransform } from "framer-motion";
import { SwipeBox } from "./SwipeBox";
const App: FunctionComponent = () => {
const [cards, setCards] = useState([
const onClic = () => {
animateCardSwipe({ x: -1400, y: 0 });
}; <div
style={{
width: "400px",
height: "300px",
background: `${card.background}`
}}
>
) : (
</div>
);
};
export default App;
Solution 1:[1]
The problem is that the animation is happening on mount and you're updating state twice inside the animateCardSwipe
function:
const animateCardSwipe = (animation: { x: number, y: number }) => {
setAnime({ ...anime, animation });
setTimeout(() => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
}, 200);
};
I personally like to use a more imperative approach here for starting the animation using animate
:
const animateCardSwipe = (animation: { x: number, y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
This implementation also waits for the animation to complete before rearranging the cards.
Full example:
import React, { FunctionComponent, useState } from "react";
import {
animate,
useAnimation,
useMotionValue,
useTransform,
MotionValue,
motion,
} from "framer-motion";
interface Props {
animate?: { x: number; y: number };
style: {
x?: MotionValue;
y?: MotionValue;
zIndex: number;
rotate?: MotionValue;
};
card: { text: string; background: string };
}
const SwipeBox: React.FunctionComponent<Props> = (props) => {
return (
<motion.div
animate={props.animate}
className="card"
style={{
...props.style,
background: "white",
borderRadius: "8px",
display: "grid",
top: 0,
left: 0,
placeItems: "center center",
position: "absolute",
width: "100%",
}}
transition={{ duration: 1 }}
>
{props.children}
</motion.div>
);
};
const App: FunctionComponent = () => {
const controls = useAnimation();
console.log(controls);
const [cards, setCards] = useState([
{ text: "Up or down", background: "red" },
{ text: "Left or right", background: "green" },
{ text: "Swipe me!", background: "gray" },
{ text: "Swipe me!", background: "purple" },
{ text: "Swipe me!", background: "yellow" },
]);
const x = useMotionValue(0);
const y = useMotionValue(0);
const animateCardSwipe = (animation: { x: number; y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
const [anime, setAnime] = useState<{ animation: { x: number; y: number } }>({
animation: { x: 0, y: 0 },
});
const rotate = useTransform(x, [-950, 950], [-30, 30]);
const onClickLeft = () => {
animateCardSwipe({ x: 1400, y: 0 });
};
const onClickRight = () => {
animateCardSwipe({ x: -1400, y: 0 });
};
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<div>
{cards.map((card, index) =>
index === cards.length - 1 ? (
<SwipeBox
animate={anime.animation}
card={card}
key={index}
style={{ x, y, zIndex: index, rotate: rotate }}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
) : (
<SwipeBox
card={card}
key={index}
style={{
zIndex: index,
}}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
)
)}
</div>
<div style={{ zIndex: 999 }}>
<button onClick={onClickRight}>??</button>
<button onClick={onClickLeft}>?</button>
</div>
</div>
);
};
export default App;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |