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-motionlibrary that aids in creating animations.useMotionValue: A hook fromframer-motionthat creates a motion value, which is a value that can be animated.useTransform: A hook fromframer-motionused to create a new motion value by transforming another.animate: A function fromframer-motionto animate a motion value.useEffect: A hook fromreactthat lets you perform side effects in your function components.useState: A hook fromreactthat 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: Roundscountto 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.