Typing animation with Framer Motion and React
These are notes on how to create a text typing effect in a React-based (Next.js) application with Framer Motion library, including a link to the complete code example on GitHub.
Full code preview
Here is a full code of React component responsible for a text typing effect.
"use client";
/**
* Importing necessary modules from their respective packages.
*
* `motion` is a component from the framer-motion library used to create animations.
* `useMotionValue` is a hook from the framer-motion library that creates a motion value.
* `useTransform` is a hook from the framer-motion library to create a new motion value by transforming another.
* `animate` is a function from the framer-motion library to animate a motion value.
*/
import { motion, useMotionValue, useTransform, animate } from "framer-motion";
/**
* Importing necessary hooks from React.
*
* `useEffect` is a hook that lets you perform side effects in your function components.
* `useState` is a hook that lets you add state to your functional components.
*/
import { useEffect, useState } from "react";
// Type definition for the props expected by the AnimatedText component.
type AnimatedTextProps = {
text: string; // The text to be animated.
};
/**
* AnimatedText is a component that gradually reveals text from start to end, one character at a time.
*
* @param {string} text - The text to be animated.
*/
export default function AnimatedText({ text }: AnimatedTextProps) {
// `count` is a motion value that starts at 0 and will animate up to the length of the text.
const count = useMotionValue(0);
// `rounded` is a transformed motion value of `count`, rounding it to the nearest whole number.
const rounded = useTransform(count, (latest) => Math.round(latest));
// `displayText` is a transformed motion value of `rounded`, slicing the text to the current count.
const displayText = useTransform(rounded, (latest) => text.slice(0, latest));
// `animationCompleted` is a state variable to keep track of whether the animation has completed.
const [animationCompleted, setAnimationCompleted] = useState(false);
useEffect(() => {
/**
* Initiating the animation of the `count` motion value from 0 to the length of the text.
* The animation is linear over a 10 second duration.
* An `onUpdate` callback is specified to check if the animation is complete, and if so, `setAnimationCompleted` is called with `true`.
*/
const controls = animate(count, text.length, {
type: "tween",
duration: 10,
ease: "linear",
onUpdate: (latest) => {
if (latest === text.length) {
setAnimationCompleted(true);
}
},
});
// Returning a cleanup function to stop the animation when the component is unmounted.
return controls.stop;
}, []); // Empty dependency array means this useEffect will only run once, similar to `componentDidMount`.
return (
/**
* Rendering a paragraph element with a class of `animation-completed` if the animation is complete,
* otherwise, it renders with an empty class string.
* Inside the paragraph, a `motion.span` element is rendered with the `displayText` motion value.
*/
<p className={animationCompleted ? "animation-completed" : ""}>
<motion.span>{displayText}</motion.span>
</p>
);
}
Code breakdown
Let’s breakdown the code presented above.
Import statements
import { motion, useMotionValue, useTransform, animate } from "framer-motion";
import { useEffect, useState } from "react";
In this section, modules are imported from the framer-motion
library and the react
library. The framer-motion
library provides tools for creating animations, while react
provides hooks for managing state and side effects.
motion
: A component from theframer-motion
library that aids in creating animations.useMotionValue
: A hook fromframer-motion
that creates a motion value, which is a value that can be animated.useTransform
: A hook fromframer-motion
used to create a new motion value by transforming another.animate
: A function fromframer-motion
to animate a motion value.useEffect
: A hook fromreact
that lets you perform side effects in your function components.useState
: A hook fromreact
that lets you add state to your functional components.
Type definition
type AnimatedTextProps = {
text: string; // The text to be animated.
};
Here, a type definition is created for the props that the AnimatedText
component expects to receive. It specifies that a text
prop of type string
is expected.
AnimatedText component
export default function AnimatedText({ text }: AnimatedTextProps) {
//...
}
The AnimatedText
component is defined as a default export, which accepts a single prop text
of type string
.
Motion values and transformed values
const count = useMotionValue(0);
const rounded = useTransform(count, (latest) => Math.round(latest));
const displayText = useTransform(rounded, (latest) => text.slice(0, latest));
Three motion values are created:
count
: Initializes at 0 and will animate up to the text length.rounded
: Roundscount
to the nearest whole number.displayText
: Slices the text to display characters up to the current count.
State variables
const [animationCompleted, setAnimationCompleted] = useState(false);
A state variable animationCompleted
is declared with an initial value of false
to track the animation's completion.
useEffect Hook
useEffect(() => {
const controls = animate(count, text.length, {
type: "tween",
duration: 10,
ease: "linear",
onUpdate: (latest) => {
if (latest === text.length) {
setAnimationCompleted(true);
}
},
});
return controls.stop;
}, []);
Within a useEffect
hook, the animate
function is invoked to animate the count
motion value from 0 to the text length over a 10-second duration.
The onUpdate
callback checks if the animation is complete, and if so, setAnimationCompleted
is called with true
.
A cleanup function is returned to stop the animation if the component is unmounted.
Rendering
return (
<p className={animationCompleted ? "animation-completed" : ""}>
<motion.span>{displayText}</motion.span>
</p>
);
A paragraph element is rendered with a class of animation-completed
if the animation is complete, otherwise, it renders with an empty class string.
Inside the paragraph, a motion.span
element is rendered displaying the displayText
motion value, revealing the text character by character as the count
motion value animates.