Animate a Hamburger Menu with Framer Motion
Tags: react, framer, animation, javascript
Published: 2021-11-05 19:13:22

Welcome back to my backyard lab, where I do my experiments drinking a lot of coffee to keep the pace 😂.

Currently, in my spare time, I'm working on my personal website and I want to share with you my process on how I built and animate the hamburger menu of the header.

---

#Preface

Animations are cool!! So why not complicate your life by animating some sections of your personal website? There is no better place to do it...

But...since I'm not an animation Guru, I asked for help from [Framer Motion](https://www.framer.com/motion), a React animation library by [Framer](https://www.framer.com).

My animation is pretty simple, the SVG has two lines (one is wider), on click/tap, the shorter one stretches reaching the max length and then I rotate both lines and create the X shape.

In this article I will show you 2 solutions that I implemented, called respectively:

* '**I don't know Framer Motion' solution (a.k.a. It works solution).**
* **'(Maybe) I know Framer Motion a little bit more' solution.**

---

#Design

Since I want to use SVG, firstly I created 3 shapes in [Figma](https://www.figma.com), representing my animation states. Every shape is inside a 24 x 24 box.

The first shape represent the 'closed' state:

![Hamburger - Closed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xh62wotmf5bvss0q1fqw.png)

The second, represent the middle state:

![Hamburger - Animating](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jcp18osuv7otzkjzgl19.png)

The last one, represent the 'open' state, when animation is completed:

![Hamburger - Open](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jivn3ze292ob5da7tu9j.png)

The following are the 3 SVG exported from Figma.

```xml
<!-- CLOSED STATE -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 <line y1="9.5" x2="24" y2="9.5" stroke="#FFFFFF"/>
 <line y1="14.5" x2="15" y2="14.5" stroke="#FFFFFF"/>
</svg>
```

```xml
<!-- MIDDLE STATE -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 <line y1="9.5" x2="24" y2="9.5" stroke="#FFFFFF"/>
 <line y1="14.5" x2="24" y2="14.5" stroke="#FFFFFF"/>
</svg>
```

```xml
<!-- OPEN STATE -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 <path d="M3.06061 2.99999L21.0606 21" stroke="#FFFFFF"/>
 <path d="M3.00006 21.0607L21 3.06064" stroke="#FFFFFF"/>
</svg>
```

---

#Tuning

The first problem I faced was about the line tags used in the first two SVG and the path used on the third.
So I decided to align everything with the path tag and I used this 'formula' that I found online:

```xml
d="Mx1 y1Lx2 y2"
```

So the first 2 SVG have become:

```xml
<!-- CLOSED STATE -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 <path d="M0 9.5L24 9.5" stroke="#FFFFFF"/>
 <path d="M0 14.5L15 14.5" stroke="#FFFFFF"/>
</svg>
```

```xml
<!-- MIDDLE STATE -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 <path d="M0 9.5L24 9.5" stroke="#FFFFFF"/>
 <path d="M0 14.5L24 14.5" stroke="#FFFFFF"/>
</svg>
```

---

#Defining Variants

So, now that every SVG is aligned, I can start tuning the variants needed by Framer Motion in order to work.

[Variants](https://www.framer.com/docs/animation/#variants), combined with the motion component, let you define animations in a declarative way.
I created two objects where each key represents the state of the animation and every value contains the information about it, in my case the *d* attribute of the SVG path.

### Path 1
This is the top line/path. This line doesn't have a middle state so the object represents only the initial and final state.

```ts
const path01Variants = {
 open: { d: 'M3.06061 2.99999L21.0606 21' },
 closed: { d: 'M0 9.5L24 9.5' },
}
```

### Path 2
This line is the bottom one and since it is the only one that has a middle state, the following object contains three keys/states.

```ts
const path02Variants = {
 open: { d: 'M3.00006 21.0607L21 3.06064' },
 moving: { d: 'M0 14.5L24 14.5' },
 closed: { d: 'M0 14.5L15 14.5' },
}
```

---

#The 'I don't know Framer Motion' solution

Okay...what I've to do is pretty straightforward:

- click on the SVG
- start the animation
- repeat!


The problem I faced with variants and the animate property was that I was not able to create a sequence between animation states.
I couldn't start from 'closed', switch to 'moving' and finally reach the 'open' state.
Since I'm using React, I thought that my component state could have more than a boolean-like (open/close) value so I created an 'animation' state with 3 possible values: 'closed', 'moving' and 'open'.

But how to sequence them? Well...a good old setTimeout came to my rescue...

```tsx
const [animation, setAnimation] = useState('closed');
const onClick = () => {
  setAnimation('moving');
  setTimeout(() => {
    setAnimation(status === 'closed' ? 'open' : 'closed');
  }, 200);
};
```

and my return statement is:

```tsx
<button onClick={onClick}>
  <svg width='24' height='24' viewBox='0 0 24 24'>
    <motion.path
      stroke='#FFFFFF'
      animate={animation}
      variants={path01Variants}
    />
    <motion.path
      stroke='#FFFFFF'
      animate={animation}
      variants={path02Variants}
    />
  </svg>
</button>
```

Here the `animate={animation}` changes its state with the value inside my React state and then the proper variant inside 'pathXXVariants' is used.
I start with the 'moving' state and after 200ms I switch to open or closed (it depends on the previous state).

---

#The '(Maybe) I know Framer Motion a little bit more' solution

The previous solution works, but I find the 'setTimeout' as a loophole to do something that probably the library can handle in some different way and most important, even if probably this is not the case, using setTimeout and React come with some caveats so I could have used also something more 'React' as well.

Anyway, diving into the documentation a little bit more I found a React hook that could be helpful for my scenario.

The [useAnimation](framer.com/docs/animation/#component-animation-controls) hook creates an [AnimationControls](https://www.framer.com/docs/utilities/#animationcontrols)
object that has some utility methods which I can use to fine-tune my animation.
With AnimationControls I can start an animation and since this method returns a Promise I can also *await* that the animation ends and start the following one. So as you can imagine we have more control 😎.

I changed the React state back to a boolean and created 2 AnimationControls, one for each path.

```tsx
const [isOpen, setOpen] = useState(false);
const path01Controls = useAnimation();
const path02Controls = useAnimation();
```

our *onClick* handler now is a little bit more complicated but more Framer Motion friendly:

```tsx
const onClick = async () => {
  // change the internal state
  setOpen(!isOpen);
  
  // start animation
  if (!isOpen) {
    await path02Controls.start(path02Variants.moving);
    path01Controls.start(path01Variants.open);
    path02Controls.start(path02Variants.open);
  } else {
    path01Controls.start(path01Variants.closed);
    await path02Controls.start(path02Variants.moving);
    path02Controls.start(path02Variants.closed);
  }
};
```

I decided to tune the duration of the animation directly on the JSX but I could have also inserted it as the second argument of the *start* method or in the variant itself.

So the final JSX...

```tsx
<button onClick={onClick}>
  <svg width='24' height='24' viewBox='0 0 24 24'>
    <motion.path
      {...path01Variants.closed}
      animate={path01Controls}
      transition={{ duration: 0.2 }}
      stroke='#FFFFFF'
    />
    <motion.path
      {...path02Variants.closed}
      animate={path02Controls}
      transition={{ duration: 0.2 }}
      stroke='#FFFFFF'
    />
  </svg>
</button>
```

and here a little demo 😇

{% codesandbox silly-parm-u70gt runonclick=1 %}

---

#Final Thoughts

This is it!

I hope you enjoyed this little tutorial and if you found it helpful, drop a like or a comment.

If you know Framer Motion and you want to share your thoughts or an alternative/better solution...please do it!!!

If you think that Framer Motion is an overkill for this little animation...yeah you are probably right but it was fun learning something new 😉.

Thanks for reading!

See ya 🤙