Flipping text with framer-motion



What we are building
Let's get started
First, we need to install the framer-motion library. You can do this by running the following command:
npm install framer-motion
Next, we are going to create a page to showcase the component.
Let's make a file called ìndex.js
and add the following code:
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Rick Astley</h1>
<h2>
He's never gonna <span>give you up</span>
</h2>
</div>
);
}
And let's add some styles for the page. Create a file called styles.css
and add the following code:
body,
html {
margin: 0;
}
.App {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh; /* adjust as needed */
text-align: center; /* for centering the text */
font-family: sans-serif;
color: #023047;
}
.App h1 {
font-size: 48px;
font-weight: 300;
line-height: 1rem;
letter-spacing: 0.45rem;
}
.App h2 {
display: inline-block;
padding-right: 10rem;
color: #4a4e69;
font-size: 32px;
font-weight: 300;
}
The TextLoop component
Now, let's create a new file called TextLoop.js
and add the following code:
import React, { useState, useEffect } from "react";
import { AnimatePresence, motion } from "framer-motion";
const variants = {
initial: {
y: -20,
opacity: 0,
},
fadeIn: {
zIndex: 1,
y: 0,
opacity: 1,
},
exit: {
zIndex: 0,
opacity: 0,
},
};
export const TextLoop = ({ words, className }) => {
const [index, setIndex] = useState(0);
useEffect(() => {
const timeoutId = setTimeout(() => {
let next = index + 1;
if (next === words.length) {
next = 0;
}
setIndex(next);
}, 3 * 1000);
return () => clearTimeout(timeoutId);
}, [index, setIndex]);
return (
<AnimatePresence>
<span className={className}>
<motion.span
style={{ position: "absolute" }}
variants={variants}
key={index}
initial="initial"
animate="fadeIn"
exit="exit"
layout
transition={{
y: { type: "spring", stiffness: 300, damping: 100 },
opacity: { duration: 0.5 },
}}
>
{words[index]}
</motion.span>
</span>
</AnimatePresence>
);
};
That's a lot of code so let's break it down:
- We define the
variants
object that will be used to animate the text. - We create the
TextLoop
component that receives an array of words and a className as props. - We added a state variable
index
to keep track of the current word to show. - We use the
useEffect
hook to update theindex
every 3 seconds. - We use the
AnimatePresence
component to fade out the text when it is removed from the DOM.
Using the TextLoop component
Go back to the ìndex.js
file and add the following code:
import "./styles.css";
import { TextLoop } from "./TextLoop";
const Word = ({ color, children }) => (
<span style={{ color: color }} className="word">
{children}
</span>
);
const words = [
<Word color="blue">give you up</Word>,
<Word color="red">let you down</Word>,
<Word color="lime">run around</Word>,
<Word color="purple">desert you</Word>,
];
export default function App() {
return (
<div className="App">
<h1>Rick Astley</h1>
<h2>
He's never gonna <TextLoop className="text-loop" words={words} />
</h2>
</div>
);
}
What we did is create a new Word
component to handle the styles of the sliding text. Then we created an array of "words" and passed it to the TextLoop
component along with a className for styling the container.
Now let's add those styles to the styles.css
file:
.text-loop {
margin-left: 8px;
position: relative;
display: inline;
white-space: nowrap;
}
.word {
font-style: italic;
font-weight: bold;
}
And that's it! You should now see the text loop animation in action.