Creating shimmering text, color change text in Loader using Framer Motion, Next.js 13
Published: 2023-08-17 14:12:51


{% embed https://youtu.be/lzSJmvoDJcY %}

While building my last app, I was fascinated by the Loader with changing color company name that our designers provided. It is really calming to look at it and wait in meditation until the page loads 😄.
Install Next.js 

```
$ npm create next-app@latest
Need to install the following packages:
  create-next-app@13.4.16
Ok to proceed? (y) y
√ What is your project named? ... animated-loader
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like to use `src/` directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to customize the default import alias? ... No / Yes
```
Run the development server

```
npm run dev
```
Clean up all unnecessary code, we don't need it anymore.
For this project, I'll use Styled Components. Don't forget to add "use client" at the top of each `styled.js` file. 
Let's create our basic styled navbar component first and adding it to our global layout file in the project. If you're not familiar with what a layout file is - it's basically a new file type introduced in Next.js v13 which can be used to create the layout for your site. By default, Next.js would create a layout file for you in the root of your app named `layout.js`. We can import there header with menu and footer. 

```
npm i styled-components
```
In the `src` directory, create a `lib `folder and add a `registry.js` file inside with the following code:


```javascript 
"use client";

import React, { useState } from "react";
import { useServerInsertedHTML } from "next/navigation";
import { ServerStyleSheet, StyleSheetManager } from "styled-components";

export default function StyledComponentsRegistry({ children }) {
  // Only create stylesheet once with lazy initial state
  // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
  const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());

  useServerInsertedHTML(() => {
    const styles = styledComponentsStyleSheet.getStyleElement();
    styledComponentsStyleSheet.instance.clearTag();
    return <>{styles}</>;
  });

  if (typeof window !== "undefined") return <>{children}</>;

  return (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children}
    </StyleSheetManager>
  );
}
```

Now wrap our app with the `StyledComponentsRegistry`. Navigate to the `layout.js` file in the `app` folder and add it:


```javascript
import "./globals.css";
import { Inter } from "next/font/google";
import { Suspense } from "react";
import Loading from "./loading";
import { Menu } from "@/components/navigation/Navigation";
import StyledComponentsRegistry from "@/lib/registry";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "Animated loader",
  description: "animated loader",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <div className="main">
          <div className="gradient" />
        </div>
        <StyledComponentsRegistry>
          <main className="app">
            <Menu />
            <Suspense fallback={<Loading />}>{children}</Suspense>
          </main>
        </StyledComponentsRegistry>
      </body>
    </html>
  );
}
```
In the same `app` folder create a file `loading.js`: 

```javascript
const Loading = () => {
  return <div>Loading...</div>;
};

export default Loading;
```
 We'll pass this to the `fallback` of the `Suspense` component as our future animated loader.

I've also created two pages: "About" and "Movies". 

![Menu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28zkiybq51l40dz793hf.png)
Let's fetch movies from [https://developer.themoviedb.org/docs](https://developer.themoviedb.org/docs). You'll need to create account and get API key. I usually keep that kind of stuff in `.env` file. 
Next, in the `app` directory, create two folders: `about` and `movies`, and create a `page.js` file in each folder for the respective pages.

Add the following code to movies/page.js:

```javascript
import Movies from "@/components/Movies/Movies";

export async function getMovies() {
  let res = await fetch(
    `https://api.themoviedb.org/3/trending/movie/day?api_key=${process.env.NEXT_PUBLIC_TMDB_API}`
  );
  await new Promise((resolve) => setTimeout(resolve, 3000));
  return res.json();
}

export default async function MoviesPage() {
  const { results } = await getMovies();

  return <Movies movies={results} />;
}
```
In `src` directory create the `components` folder. Inside it, create another folder named `Movies`. In this folder, create `Movies.jsx` and `Movies.styled.js` files. 
In `Movies.jsx`, create a functional component: 

```javascript
import { Section } from "../Section/Section";
import Image from "next/image";

export default function Movies({ movies }) {
  console.log("movies", movies);
  return (
    <Section>
      <h1>Movies</h1>
      <ul>
        {movies.map((movie) => (
          <li key={movie.id}>
            <Image
              src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`}
              alt={movie.title}
              width={200}
              height={500}
            />
            <p>{movie.title}</p>
          </li>
        ))}
      </ul>
    </Section>
  );
}

```
You'll need to modify `next.config.js` to display images: 

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    domains: ["image.tmdb.org"],
    remotePatterns: [
      {
        protocol: "https",
        hostname: "**.tmdb.org",
        port: "",
        pathname: "/t/p/w200**",
      },
    ],
  },
};

module.exports = nextConfig;
```
Now we have our movies:

![Movies](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1iueh6ut93190rzueiby.png)
Let's add some styling. Well that looks much better!

![Styled movies](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56n8mq5ukniq5o2km70t.png)

Now, let's move on to creating our loader. To achieve that, we need to do a little magic in Figma. Create two ellipses with a size around 300px and add the company name text inside them.

![Start figma](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qaxnsb44vaxyen6l8org.png)
I used the following colors: 

- Blue: #8DCBE6
- Green: #9DF1DF
- Yellow (for contrast-changing color): #FFEA20

The text is blue, size 40 and font "Jua" extra bold. Right-click and choose "Outline Stroke". 
Now we need to create our yellow letters. For that I created every letter separately. Then right click and choose again "Ouline stroke". That way we create svg letters one by one.

![Transform text to svg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5x0y1m6l3lt1hdbcf7j4.png)
Now do this for each letter. Don't forget to change their color to yellow. You have to get something like this:

![Create yellow letters](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fl4poyhd1fgc2ctggfth.png)
We need yellow letters separately to animate them individually.
Next step is to place yellow letters above blue ones that way:

![Place yellow letters above blue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h0t6a64ib8buu9vwwm48.png)
Last step is we need to group blue svg text and finally group all yellow letters with blue text.

![Group](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7ad9xxj5v3nee7o9ojy.png)
Now export 3 files (two ellipses and group with letters) from Figma to the `public/images` folder in the project. 
Create the `Loader `folder in `components`. Create the file `Loader.jsx`. It will be our common loader component. 
First we need to wrap the loader in container. Create `Loader.styled.js`: 

```javascript
export const Container = styled.div`
  position: relative;
  height: 80vh;
  width: 100%;
  margin: 0 auto;
  display: flex;
  justify-content: center;
  align-items: center;
`;
```
Now add `Loader Container` with ellipses. Now we create ellipse animation. I add ellipses as `background image` of `::before` and `::after` of `LoaderContainer`. One ellipse rotates clock wise, another in opposite direction. Import `keyframes` from `styled-components`.

```javascript
const rotate = keyframes`
  0% {
  transform: rotate(1deg) scale(1);
}
  40% {
    transform: rotate(180deg) scale(0.8);
  }
  100% {
    transform: rotate(360deg) scale(1);
  }
`;

const rotateOpposite = keyframes`
0% {
  transform: rotate(1deg) scale(1);
}

  40% {
    transform: rotate(-180deg) scale(0.8);
  }
  100% {
    transform: rotate(-360deg) scale(1);
  }
`;
```
Add the animation to ellipses: 

```javascript
export const LoaderContainer = styled.div`
  position: absolute;
  transform: translate(-50%, -50%);
  left: 50%;
  top: 50%;
  height: 300px;
  width: 300px;
  margin: 0 auto;

  &::before {
    content: " ";
    display: block;
    position: absolute;
    height: 300px;
    width: 300px;
    background-image: url(/images/blueEllipse.svg);
    background-repeat: no-repeat;
    background-position: center;

    animation: ${rotate} 4s linear infinite;
  }
  &::after {
    content: " ";
    display: block;
    position: absolute;
    height: 300px;
    width: 300px;
    background-image: url(/images/greenEllipse.svg);
    background-repeat: no-repeat;
    background-position: center;
    animation: ${rotateOpposite} 4s linear infinite;
  }
`;
```
Import `Container` and `LoaderContainer` to `Loader.jsx`. From `letters.svg` in images create component. You can use SVGR or do it manually. It will look like this: 

![Letters svg component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mgr2sv77lz3fsq0io2fo.png)
Import `Letters` to `Loader` component:

```javascript
"use client";

import { Letters } from "./Letters";
import { Container, LoaderContainer } from "./Loader.styled";
export const Loader = () => {
  return (
    <Container>
      <LoaderContainer />
      <Letters />
    </Container>
  );
};
```
Now the loader looks like this: ellipses are rotating, letters are static.

![Static letters](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vfyvkv8io0fo119a0jiw.png)
Install Framer Motion

```
npm i framer-motion
```
Import `{motion}` from `framer-motion` in `Letters.jsx`. Now we need to isolate all yellow letters. Add `id` attributes to identify each letter.
Replace `<svg>` to `<motion.svg>` and `<path>` to `<motion.path>` (only for yellow letter)

```javascript
<motion.path
        id="m" //for letter M
        d="M25.36 25.64C25 //.... rest"
        fill="#FFEA20"
      />

```
Create a `delay` function: 

```javascript
const delay = (i) => {
  return 0 + i * 0.2;
};
```
Pass `i` in each `motion.path` tag that way that each letter starts the animation a bit later than the previous one. Set the duration to 1 second. Add `animate `and `transition `properties to each `motion.path`, using the `delay` function for the delay of each letter (e.g., `delay(0)` for the first letter and `delay(9)` for the last):

```javascript
<motion.path
        id="m"
        animate={{ opacity: [0, 1, 0] }}
        transition={{
          duration: duration,
          ease: "linear",
          repeat: Infinity,
          delay: delay(0),
        }}
        d="M25.36 25.64C25.3333 ... etc"
        fill="#FFEA20"
      />
      <motion.path
        id="o1"
        animate={{ opacity: [0, 1, 0] }}
        transition={{
          duration: duration,
          ease: "linear",
          repeat: Infinity,
          delay: delay(1),
        }}
        d="M33.1513 11.0982C34.0313 ...etc"
        fill="#FFEA20"
      />
      <motion.path
        id="v"
        animate={{ opacity: [0, 1, 0] }}
        transition={{
          duration: duration,
          ease: "linear",
          repeat: Infinity,
          delay: delay(2),
        }}
        d="M66.77 11.5714C66.8767 ...etc"
        fill="#FFEA20"
      />
```
Import the Loader component to loading.js in the app folder and check how it works.
{% embed https://youtu.be/13zwqx3paVw %}
You can choose any colors you like, adjust the duration, increase the delay, and experiment with different animations. The possibilities are endless, and it's all up to your imagination and creativity!