Who’s ready for a cool hamburger menu animation?
Even a simple web animation can take a website to a whole other level, improving user experience and keeping the aesthetic relevant.
Hamburger menus are symbolic to mobile or tablet devices. In fact, users are so familiar with the two- or three-bar menu that hamburger menus have made their way onto regular screen sizes as well.
See the Pen Animated CSS Hamburger Menu by Klea Merkuri (@thehelpfultipper) on CodePen.
There are really two parts to the animated hamburger menu:
- the bars creating the menu – whether two or three
- the animation that transforms the hamburger into an “X”
The animation indicates the interactivity between the element and the user.
When the hamburger turns into an “X”, it signals to the user that (a) the menu was clicked open and that (b) it can be clicked again to close.
We’ve created a two-bar animated hamburger menu before here on THT during the product landing page project (advanced version).
But in this post, I’ll be creating a three-bar hamburger menu inspired by that of Starbucks‘ mobile menu.
Start by building a simple navigation bar for demo purposes.
There are going to be two components on this navbar: (1) a logo (we’re doing ours…of course) and (2) the hamburger menu.
<nav>
<div id="logo">
<img src="https://thehelpfultipper.com/wp-content/uploads/2021/12/tht-site-logo1-removebg-preview.png" alt="The Helpful Tipper logo" />
</div>
<div id="hbg-menu">
<span></span>
<span></span>
<span></span>
</div>
</nav>
Inside #logo there lies the logo image while inside #hbg-menu there are three span elements – each span is a menu bar.
Make all elements on the page account for borders and paddings in their respective width and height dimensions using box-sizing. Then remove default margins and paddings from the body.
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
For the navbar, place it fixed at the top of the page. Use flexbox to distribute space by:
- bringing #logo to the left-hand side
- pushing #hbg-menu to the right-hand side
- centering the two children elements in the center of the nav
nav {
background-color: #FCF5E5;
box-shadow: 0 2px 2px #E2DFD2;
width: 100%;
height: 80px;
position: fixed;
display: flex;
align-items: center;
justify-content: space-between;
}
#logo {
padding: 0 20px;
}
Moving onto the menu wrapper, #hbg-menu, give it some dimensions including:
- display of flex to center the menu bars in the center of the container
- a border-radius so that the wrapper is a circle
The circle part might be somewhat confusing, but it’s part of the animation technique I’ll be employing. I give it a background color of pink so we can see what’s going on (I remove this later).
Part of the animated spin will be applied to the #hbg-menu wrapper to mimic a seamless and continuous spin! Keep on following and it’ll all make sense 😉
#hbg-menu {
margin-right: 5px;
display: flex;
justify-content: center;
width: 70px;
height: 70px;
border-radius: 50%;
background: pink;
position: relative;
}
#hbg-menu:hover {
cursor: pointer;
}
Note: The wrapper #hbg-menu takes on a relative position because the children span will be placed absolutely to their parent.
Now we need to create the three menu bars. I start by giving each span form and an absolute position at the very top of their parent.
span {
display: block;
background-color: black;
width: 20px;
height: 2px;
border-radius: 5px;
position: absolute;
top: 0;
}
Continue by spacing the bars out using the CSS top property along with some simple maths.
span:nth-of-type(1) {
top: calc(50% - 6px);
}
span:nth-of-type(2) {
top: 50%;
}
span:nth-of-type(3) {
top: calc(50% + 6px);
}
Note that:
- I use the CSS pseudo-class :nth-of-type() to grab the span I want among the three siblings. You can opt for an identifier, like a class or ID, instead.
- I’m basing the spacing of each span on personal preference (as well as similarity to the Starbucks reference).
What we’ve got so far:
Now’s the fun part – animation! Ready for the challenge?
When a user clicks on the menu bar, three things must happen:
- the first and third bars join by shifting to the center of the hamburger menu (where the second bar is located)
- both bars – the existing center bar plus the joined first and third bars – rotate clock-wise but by differing degrees of rotation
- clicking again reverses all actions (aka the rotations and shift of the first and third bars)
Follow the steps below to apply all animations.
I’ll use JavaScript to detect when the click event occurs on the hamburger menu and to assign CSS classes.
// Grab the menu from the DOM
const menu = document. querySelector('#hbg-menu');
// Detect click event on hamburger menu
menu.addEventListener('click', () => {
...
});
Anything inside the callback function, () => {}
, will execute once menu (which points to #hbg-menu) is clicked.
2) Slide to center animation
First, I’m going to create the animation that will make the top and bottom menu bars slide to the center, overlapping with the middle menu bar.
There are going to be two classes:
- top-center: moves the top menu bar (first
span
) to the center - bottom-center: moves the bottom menu bar (third
span
) to the center
.top-center {
transform: translateY(6px);
}
.bottom-center {
transform: translateY(-6px);
}
The distance of the translation corresponds to that of the positioning we used earlier for creating the menu bars. Recall, I moved the first span 6px up and the third span 6px down relative to the halfway point of the parent container.
But just defining these two animation classes isn’t going to make anything happen unless we apply them to the appropriate span
s.
This is when we pop back into our callback!
What should happen:
When the user clicks on the menu button, the first span
gets assigned the top-center class while the third span
gets assigned the bottom-center class.
Grabbing each of the hamburger menu bars
Since we’ll be working with each of the three span
elements, we need to store them in a variable(s) to point at each one.
I’ll use JavaScript selector querySelectorAll to get a list of all three spans. Then I loop through the list, referencing each span using its index.
const bars = document.querySelectorAll('span');
// Inside the callback of the event listener
bars.forEach( (bar, i) => {
i === 0 && bar.classList.toggle('top-center');
});
Note: There are slight differences between the forEach and for loops in JavaScript. I opted to use forEach to save some typing!
The short-hand conditional statement above:
- looks for when the first
span
is grabbed. On the list, the first element will have an index of zero. - applies to the
span
the top-center class using the toggle() method by getting the element’s classes using the classList object.
Tip: Using the add() method of the classList object instead will require the remove() method as well when the user clicks to close the button (aka more work, more code, not necessary).
Do the same for the third span
by adding the bottom-center class using:
i === 2 && bar.classList.toggle('bottom-center')
You should have:
But, despite the top and bottom menu bars moving to the center, the move itself can hardly be called a slide. They both appear to just disappear 😳
Not quite the effect we’re going for there.
Luckily, it’s a simple matter of adding a transition to the span
elements, transition: transform .2s
. It makes all the difference!
Now we have a slide effect on the hamburger menu.
3) Rotate animation
Moving on to the last bit of the hamburger menu. This is the part where the menu bars turn into an “X” when the user clicks on the menu, then goes back to a hamburger menu with another click.
I’ll begin by rotating #hbg-menu 45 degrees after a 0.2s delay. The delay accounts for the slide animation we implemented above which has a 0.2s duration.
.turn-45 {
animation: turn-45 .2s .2s forwards;
}
@keyframes turn-45 {
100% {
transform: rotate(45deg);
}
}
Basically, you want the rotation to occur after the top and bottom menu bars move to the center because we want to see that animation play out!
Apply the turn-45 class to menu in the callback, menu.classList.toggle('turn-45')
.
Note: Make sure you don’t accidentally place it inside the bars loop!
We’ve got the first part of the “X” up and running. To get the second part, rotate the middle menu bar (which, so far, has no other transformation declared on it) a little more so we get the “X”.
.turn-90 {
animation: turn-90 .2s .2s forwards;
}
@keyframes turn-90 {
100% {
transform: rotate(90deg);
}
}
And in the callback, inside of the bars loop, add i === 1 && bar.classList.toggle('turn-90')
.
This way, we get that extra rotational round (aka the right side of the “X” seems to spin 360 degrees) that appears in the Starbucks’ mobile menu!
Getting over multiple transform styling conflicts
But, are you still wondering why we rotated the menu wrapper? Why bother doing so when we could just rotate the bars themselves?
It all comes down to the initial transformation added to the top and bottom menu bars.
The CSS transform property accepts a running list of what you want to be transformed.
For example, let’s say you want to make an element bigger (using scale()) and rotate that same element 45 degrees (using rotate()).
You can’t have two transform declarations for a single element. The correct way would be like so: transform: scale(1.5) rotate(45deg).
Notice there is no comma separating the list of transform functions! The delimiter, in this case, is white space.
So for multiple transforms, there is a single transform property per element.
However, our code doesn’t follow this directive because we need to apply a transform in the animation keyframe.
Likewise, if we included the rotation along with the translation, the result would be concurring transformations.
This means that the move to the center of the top and bottom menu bars would go unnoticed to the eye as the last thing we’d see would be the rotation.
Animating the menu wrapper allows us to declare the rotation on another element that will affect the ones we want without conflict. It’s a workaround . . . you need those now and then.
Nevertheless, we managed to achieve the animated hamburger menu we wanted.
Don’t forget to remove the background color added to the #hbg-menu – it was for demo purposes (though I admit, it grew on me 😄 )!
Our final product:
Next, we will integrate this code with a side panel menu. Ready to tackle that project?
Find the full code or the animated hamburger menu on GitHub.