Reputation: 621
I try make an text Animation with framer motion, React
The Animation work fine still.
Since I use text Animation after switch route. So after change path.
But will be lot of components.
So I try solving things inside the component.
Not animation when path change but when the props change.
Now the Animation also work but not perfect
The essence:
The First Animation Fine with "A A"
The second and third Animation not appear one by one but at once
I guess based on other post something problem with stagger children. Bit I dont know exatly what should i do that working text animation one by one when props change
More simple if I show you on codesandbox:
Data.js
const data = [
{
id: 0,
maintext: "A A",
answerButtons: [{ answerText: "Next", isCorrect: true }],
image: "",
},
{
id: 1,
maintext: "B B B B",
answerButtons: [{ answerText: "Next", isCorrect: true }],
},
{
id: 2,
maintext: "C C C C C C",
answerButtons: [{ answerText: "Next", isCorrect: true }],
},
];
export default data;
MobilBase.js
import React, { useState, useEffect } from "react";
import SpeechBubble from "../../../components/SpeechBubble/SpeechBubble";
import Data from "../../../utils/data";
import "./MobilBase.css";
const MobilBase = () => {
const [counter, setCounter] = useState(0);
const [data, setData] = useState(Data);
const nextHandler = (i) => {
if (Data[counter].answerButtons[i].isCorrect === true) {
setCounter((prevState) => {
return prevState + 1;
});
}
};
return (
<div className="base base-page-grid">
<div className="counter">
<h2>
{counter} / {data.length - 1}
</h2>
</div>
<div className="speech-bubble-outer">
<SpeechBubble maintext={data[counter].maintext} />
</div>
{data[counter].answerButtons.map((answerbutton, i) => (
<div key={i} className="base-buttons-outer">
<div>
<button
onClick={() => {
nextHandler(i);
}}
>
<strong>{answerbutton.answerText}</strong>
</button>
</div>
</div>
))}
</div>
);
};
export default MobilBase;
SpeechBubble.js
import React, { useState, useEffect } from "react";
import Animation from "./Animation";
export default function SpeechBubble(props) {
return (
<div className="frame-speech-bubble">
<div className="zumzum-animation-grid-frame">
<Animation maintext={props.maintext} />
</div>
</div>
);
}
Animation.js
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion/dist/framer-motion";
import AnimatedText from "./AnimatedText";
import "./Animate.css";
export default function Animation(props) {
// Placeholder text data, as if from API
const placeholderText = [{ type: "heading1", text: props.maintext }];
const container = {
visible: {
transition: {
staggerChildren: 0.025,
},
},
};
return (
<motion.div
className="animation-frame"
initial="hidden"
animate="visible"
variants={container}
>
<div className="container-animated-text">
{placeholderText.map((item, index) => {
return <AnimatedText {...item} key={index} />;
})}
</div>
</motion.div>
);
}
AnimatedText.js
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion/dist/framer-motion";
// Word wrapper
const Wrapper = (props) => {
// We'll do this to prevent wrapping of words using CSS
return <span className="word-wrapper">{props.children}</span>;
};
// Map API "type" vaules to JSX tag names
const tagMap = {
paragraph: "p",
heading1: "h1",
heading2: "p"
};
// AnimatedCharacters
// Handles the deconstruction of each word and character to setup for the
// individual character animations
const AnimatedCharacters = (props) => {
// Framer Motion variant object, for controlling animation
const item = {
hidden: {
x: "-200%",
color: "#0055FF",
transition: { ease: [0.455, 0.03, 0.515, 0.955], duration: 0.85 }
},
visible: {
x: 0,
color: "red",
transition: { ease: [0.455, 0.03, 0.515, 0.955] }
}
};
// Split each word of props.text into an array
const splitWords = props.text.split(" ");
// Create storage array
let words = [];
// Push each word into words array
for (const item of splitWords) {
words.push(item.split(""));
}
// Add a space ("\u00A0") to the end of each word
words.map((word) => {
return word.push("\u00A0");
});
// Get the tag name from tagMap
const Tag = tagMap[props.type];
return (
<Tag>
{words.map((word, index) => {
console.log(`return: word ${word} index: ${index}`);
return (
// Wrap each word in the Wrapper component
<Wrapper key={word.id}>
{words[index].flat().map((element, index) => {
return (
<span
style={{
overflow: "hidden",
display: "inline-block"
}}
key={word.id}
>
<motion.span
className="animatedtext spell"
style={{ display: "inline-block" }}
variants={item}
>
{element}
</motion.span>
</span>
);
})}
</Wrapper>
);
})}
</Tag>
);
};
export default AnimatedCharacters;
Upvotes: 1
Views: 932
Reputation: 4649
You should not use the loop index as the key
in the elements output from map
. Since the indices (and thus the keys) will always be the same, it doesn't let Framer track when elements have been added or removed in order to animate them.
Instead use a value like a unique id
property for each element. This way React (and Framer) will know when it's rendering a different element (vs the same element with different data). This is what triggers the animations.
Here's a more thorough explanation:
react key props and why you shouldn’t be using index
Upvotes: 2