Accessible Wavy Text Animation using React Hooks and Framer Motion
We will learn to use Framer Motion with React hooks, next to that we animate some text and keep it accessible!
In this article i'll recreate a wavy text animation we built with React Hooks and GSAP v3 but with Framer Motion.
If you would like to see the GSAP tutorial, check it out here.
For the example I created my own small text split function. This example should be usable in React frameworks like Gatsby, Next.js, etc.
Tech stack
- React for markup, templating, routing, etc.
- Framer Motion for animation
- EmotionCSS to write normal CSS, nest selectors and componentize styles
Goal
We want to create text that animates on first-load. The text should be split for each letter, then animate once smoothly.
CSS
We create some global CSS:
1/* @import for demo purposes. Don't use it in prod as its bad for performance */2@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');34body {5 margin: 0;6 background: #7700ff;7}89.App {10 font-family: poppins;11 text-align: center;12 background: radial-gradient(13 farthest-side at bottom left,14 rgba(255, 0, 255, 0.5),15 transparent16 ), radial-gradient(farthest-corner at bottom right, rgba(255, 50, 50, 0.5), transparent17 400px);1819 position: relative;2021 width: 100%;22 min-height: 100vh;2324 margin: 0;25 padding: 0;2627 display: flex;28 justify-content: center;29 align-items: center;30}
And we make the text look pretty with some css. The font-size value is based on a clamp function used in WebGL.
The function creates a range between 1600px and 320px screenwidth. Within this range it calculates a font-size between 80px and 32px.
The motion.h2 means that we use the motion component from Framer Motion and tell it to use the h2 HTML element.
1const StyledTitleElement = styled(motion.h2)`2 font-size: calc(32px + (80 - 32) * ((100vw - 320px) / (1600 - 320)));3 line-height: calc(32px + (80 - 32) * ((100vw - 320px) / (1600 - 320)));45 font-family: poppins;6 text-transform: uppercase;78 position: relative;9 display: inline-block;10 max-width: 100%;1112 word-break: break-word;13 z-index: 10;1415 color: white;16`;
Using hooks and writing markup
I'm using a timeout that updates a state inView. When inView updates it triggers the useEffect hook and replays the animation, resulting in a loop.
1export default function App() {2 // You can toggle this with intersection observer.3 const [inView, updateInView] = useState(false);45 // Don't do this in production :)6 useEffect(() => {7 let timeout = setTimeout(() => {8 updateInView(!inView);9 }, 2000);1011 return () => clearTimeout(timeout);12 }, [inView]);1314 return (15 <main className="App">16 <AnimatedTitle currentInView={inView}>level30wizards.com</AnimatedTitle>17 </main>18 );19}
Animating with Framer Motion
To start animating with Framer Motion, we need to understand some jargon.
Framer Motion uses variants to distinguish between the initial state, the animate state and the exit state.
It looks like this:
1<StyledTitleElement2 {...props}3 variants={letterContainerVariants}4 initial={"before"}5 animate={"after"}6 exit={"before"}7 key={children}8 aria-label={children} // people with assistive tech don't feel like hearing individual letters ;)9 //aria-live="polite" add this if you dynamically show the element10>
Framer Motion uses
variantsto distinguish between theinitialstate, theanimatestate and theexitstate.
Variants
Variants are pretty much just JavaScript objects that contain information for the animation.
The letterContainerVariants are set on the parent element that contains all the individual letters.
The letterVariants are set on, you guessed it, the individual letters.
1// Add staggering effect to the children of the container2export const letterContainerVariants = {3 before: { transition: { staggerChildren: 0.015 } }, // When the letters show they stagger with a duration of 0.015 seconds4 after: { transition: { staggerChildren: 0.03 } }, // When the letters hide they stagger with a duration of 0.03 seconds5};67// Variants for animating each letter8export const letterVariants = {9 // before state or initial10 before: {11 opacity: 0, // invisible!12 y: 20, // a bit lower than final position13 transition: {14 type: 'spring', //physical spring transition15 damping: 12, //how quickly the spring slows down16 stiffness: 200, // how "stiff" the spring is17 },18 },19 // after state or exit20 after: {21 opacity: 1,22 y: 0,23 transition: {24 type: 'spring',25 damping: 12,26 stiffness: 200,27 },28 },29};
Splitting words in React
1{2 {3 /* split sentences into words */4 }5 children.split(' ').map((word: string, wordI: number) => (6 <div7 key={`word-${word}-${wordI}`}8 style={{9 display: 'inline-block',10 }}11 >12 {/* split words into letters */}13 {Array.from(word).map((letter, index) => (14 <motion.span // we need motion because we will add a Framer Motion animation variant to it.15 key={`${index}-${letter}`}16 style={{17 position: 'relative',18 display: 'inline-block',19 width: 'auto',20 }}21 variants={letterVariants} // variant for the individual letter22 >23 {/* letter or space */}24 {letter === ' ' ? '\u00A0' : letter}25 </motion.span>26 ))}27 {'\u00A0'}28 </div>29 ));30}
Demo
You can check out the demo here:
Final words
That's it!
You can link this animation to an Intersection Observer to animate while scrolling, or keep it like this to animate when someone navigates.
If you want to see it in production, visit: Level30Wizards
Thanks for reading!
I hope someone somewhere learned something via this post! If you did, please consider sharing the article.
Other posts you might like
How to develop a Web AR Facefilter with React and ThreeJS / React Three Fiber in 2021
September 09, 2021For the 3 year anniversary of 🧙🏼‍♂️ Level30Wizards we wanted to build something that's related…
Creating scroll snapping blocks with GSAP v3 animations
July 01, 2020Setting up some HTML So firstly I want to say something about using a lot of div elements. Stop…
Beginner guide to web animation to get started
June 30, 2020A Creative Front-End Developer or Motion Developer will make a UI or concept come to life on the web…



