Create a Satisfying Wavy Text Animation With Framer Motion
Tags: css, react, webdev, tutorial
Published: 2022-05-05 17:59:53

Hello there! It's been a while. Although now that my [AP Computer Science](https://apstudents.collegeboard.org/courses/ap-computer-science-a) exams are over, and summer is almost here (30 days of school left), I will hopefully have more time on my hands to devote to projects and writing.

In our blog post today, we'll be looking at how we can create a satisfying wavy text animation using [Framer Motion](https://www.framer.com/motion/), [React](https://reactjs.org) and [TypeScript](https://typescriptlang.org). 

Here's a demo of the project in [CodeSandbox](https://j69kkr.csb.app/) 👇

{% codesandbox j69kkr %}

# Getting Started

I know you're eager for action, so let's begin! Start by initialising a [React] and [TypeScript](https://typescriptlang.org) project using [create-react-app](https://create-react-app.dev/). 

```zsh
npx create-react-app wavy-text --template typescript
cd wavy-text
```

For this, we only need to install one other library called [Framer Motion](https://framer.com/motion). Let's install it!

```zsh
yarn add framer-motion
# npm i framer-motion
```

Awesome! Our project is properly setup. Let's open up our `App.tsx` to get started. Let's replace the default content to get started.

```tsx
import "./styles.css";
import WavyText from "./WavyText";

export default function App() {
  return (
    <div className="App">
      <h1>Awesome Wavy Text!</h1>
    </div>
  );
}
```

Cool. Let's now switch to our `src/styles.css` file to configure some basic styling for our application. Nothing too fancy, but we want to make it look pretty.

```css
@import url("https://fonts.googleapis.com/css2?family=Lexend+Deca&display=swap");

body {
  background: linear-gradient(
    45deg,
    hsl(272deg 75% 65%) 0%,
    hsl(193deg 100% 50%) 50%,
    hsl(162deg 84% 88%) 100%
  );
}

.App {
  font-family: "Lexend Deca", sans-serif;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  justify-content: center;
  align-items: center;
}

h1 {
  color: white;
  font-size: 48px;
  user-select: none;
}
```

# Creating The Animation

Awesome! Now that we have that boring stuff setup and working, let's get into the actual meat of this project. 

Switching gears onto [React] now, let's first import what we'll need for this project and configure our props for the component. 

```tsx
import { FC } from "react";
import { motion, Variants, HTMLMotionProps } from "framer-motion";

interface Props extends HTMLMotionProps<"div"> {
  text: string;
  delay?: number;
  duration?: number;
}
```

Since we're using [Motion](https://framer.com/motion), we need to use [HTMLMotionProps](https://www.framer.com/docs/component) to forward our props onto our HTML component. 

Let's now start to create our [React function component](https://reactjs.org/docs/components-and-props) inside our file and pass our props through. 

```tsx
const Letter: FC<Props> = ({
  text,
  delay = 0,
  duration = 0.05,
  ...props
}: Props) => {

}
```

Inside here, we should take our text input and transform each letter in this string into an array of strings. For this, we can use the [Array.from()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) function in JavaScript to do achieve exactly what we want.

```tsx
const letters = Array.from(text);
```

> Do note that if you're using an international language, you might want to check out [Grapheme Splitter](https://github.com/orling/grapheme-splitter) to divide strings into individual user perceived characters, as opposed to computer perceived characters. Since our text is in English, it'd just add unnecessary complication and an extra step to our project so I'm not adding it in :)

Awesome! Let's now map individual letters in this array under another component. 

```tsx
return (
  <motion.h1
    style={{ display: "flex", overflow: "hidden" }}
    {...props}
  >
    {letters.map((letter, index) => (
      <motion.span key={index}>
        {letter === " " ? "\u00A0" : letter}
      </motion.span>
    ))}
  </motion.h1>
);
```

Our animation functionality basically works now... there's just a slight problem. The animation looks terrible. Luckily, we can use [Variants](https://www.framer.com/docs/animation/) in [Framer Motion](https://framer.com/motion) to solve our problem.

Outside (or inside - we can even declare them in a new file and import them in) our `WavyText` component, we can create two different animations for both the container and the child. 

```tsx
const container: Variants = {
  hidden: {
    opacity: 0
  },
  visible: (i: number = 1) => ({
    opacity: 1,
    transition: { staggerChildren: duration, delayChildren: i * delay }
  })
};

const child: Variants = {
  visible: {
    opacity: 1,
    y: 0,
    transition: {
      type: "spring",
      damping: 12,
      stiffness: 200
    }
  },
  hidden: {
    opacity: 0,
    y: 20,
    transition: {
      type: "spring",
      damping: 12,
      stiffness: 200
    }
  }
};
```

Now that we have that done, we can set the `variants` in our components to the appropriate animation.

```tsx
<motion.h1
  style={{ display: "flex", overflow: "hidden" }}
  variants={container}
  initial="hidden"
  animate="show"
  {...props}
>
``` 

...and in our child component:

```tsx
<motion.span key={index} variants={child}>
```

Cheers - our animation now works! We just need to import it into our `src/App.tsx` file and configure it properly.

Open up the `src/App.tsx` file now. Start by importing your component, and then delete the `<h1></h1>` element, and replace it with:

```
// import WavyText from "./WavyText";
// ...

<WavyText text="Awesome Wavy Text!" />
```

Wonderful! Our animation should now be working as we expected. On my example, I've also implemented a "replay" functionality, if you're interested into looking at the code behind that, be sure to check out [CodeSandbox](https://codesandbox.io/s/framer-motion-react-wavy-letter-text-animation-j69kkr)

# Conclusion

That's all I have for you! Hopefully you learned something new, and you use later end up using this animation to liven up your own websites! I'm also currently using this animation on [my website](https://hxrsh.in/) :)

If you'd like to see more design, a11y and related articles on my blog - do let me know! I'm eager to hear your feedback.

Enjoy the rest of your day 👋

[React]: https://reactjs.org