Tutorials
Malik Kotb
Jan 22, 2024 / Beginner / Short
A web animation tutorial on creating an animated-counter loading screen using Next.js, TailwindCSS and Framer Motion. This tutorial offers a step-by-step guide to adding this interactive element to your loading screens.
Let's start the project by setting up a Next.js app with TypeScript and utilizing the app router. Just run npx create-next-app@latest client
in the terminal.
We can clear out all the contents in the page.tsx, and global.css files, and add our own HTML and CSS, to begin with a clean slate in the application.
npm i framer-motion
.We create a black, full-screen background and place our AnimateCounter
component in the center.
page.tsx
1import AnimateCounter from "@/components/AnimatedCounter";
2
3export default function Home() {
4 return (
5 <main className="bg-black h-screen flex justify-center items-center">
6 <AnimateCounter />
7 </main>
8 )
9}
10
Create a components directory (next to the app directory) and create a new file AnimatedCounter.tsx
.
page.tsx
1"use client";
2import { MotionValue, motion, useSpring, useTransform } from "framer-motion";
3import { useEffect, useState } from "react";
4
Initialize state count
with 0 and inside a useEffect
hook update count
to 100 after 200 milliseconds, which will trigger our animation.
page.tsx
1export default function AnimateCounter() {
2 let [count, setCount] = useState(0);
3
4 useEffect(() => {
5 setTimeout(() => {
6 setCount(100);
7 }, 200);
8 }, []);
9
10 return (
11 <div>
12 <Counter value={count} />
13 </div>
14 );
15}
16
The counter component entails the main functionality of the animation.
page.tsx
1function Counter({ value }: {value: number}) {
2 let animatedValue = useSpring(value, {
3 stiffness: 50,
4 damping: 20,
5 duration: 2,
6 });
7 useEffect(() => {
8 animatedValue.set(value);
9 }, [animatedValue, value]);
10
11 return (
12 <div className="flex h-32 text-white text-9xl font-medium overflow-hidden">
13 <div className="relative w-20">
14 {[...Array(10).keys()].map((i) => (
15 <Number place={100} mv={animatedValue} number={i} key={i} />
16 ))}
17 </div>
18 <div className="relative w-20">
19 {[...Array(10).keys()].map((i) => (
20 <Number place={10} mv={animatedValue} number={i} key={i} />
21 ))}
22 </div>
23 <div className="relative w-24">
24 {[...Array(10).keys()].map((i) => (
25 <Number place={1} mv={animatedValue} number={i} key={i} />
26 ))}
27 </div>
28 </div>
29 );
30}
31
useSpring
hook from framer-motion to animate the provided number. You can configure the animation effect with properties like stiffness, damping, and duration.page.tsx
1type NumberType = {
2 place: number;
3 mv: MotionValue;
4 number: number;
5};
6
7function Number({ place, mv, number }: NumberType) {
8 let y = useTransform(mv, (latest) => {
9 let height = 128;
10 let placeValue = (latest / place) % 10;
11 let offset = (10 + number - placeValue) % 10;
12 let memo = offset * height;
13 if (offset > 5) {
14 memo -= 10 * height;
15 }
16 return memo;
17 });
18
19 return (
20 <motion.span style={{ y }} className="absolute inset-0 flex justify-center">
21 {number}
22 </motion.span>
23 );
24}
25
useTransform
dynamically calculates the vertical position for each digit.