Building a Smooth Sliding Mobile Menu

It’s possible to create user interface animations in the browser that are as buttery smooth as native app animations. There are a few important techniques that you’ll need to know in order to achieve that level of performance.

This site is built to be responsive, meaning when you size down your browser window or visit on a mobile device the design adjusts to fit.

The biggest change is the top navigation menu, when the width of the viewport becomes narrow enough it will collapse into what is now commonly called a “burger button”. I guess it resembles a burger from the side? In any case, tapping that button will slide in a menu specifically designed for mobile devices. Give it a try, I’ll wait.

You might notice that the performance of the menu transition is smooth and silky, and the burger button responds instantly to your finger tap. On modern devices it’s almost indistinguishable from the performance you’d expect in native apps.

In this post I’m going to run through all the implementation techniques you’ll need to know in order to get that level of animation performance on your own websites or web apps. Let’s get into it!

1. Build out the HTML

The first step is to build out the HTML that we’ll need for the page and the menu. You’ll need two containers, one for everything on your page, and one for the menu. I’m using UL and DIV elements here, but you can use whatever makes the most semantic sense on your page. I’ve also added a link that will eventually act as the way to toggle the menu open and closed.

My New Site


The order of the menu and the page elements doesn’t matter, although semantically the menu makes the most sense at the top of your page.

2. Style the Menu with CSS

We’ll need some basic styles in order for the menu to appear on the top right hand side of the screen and remain a specific width regardless of the screen size. Feel free to adjust these styles so the menu looks exactly how you’d like.

I’ve chosen to give the menu a width of 240px for a couple of reasons, it feels like a good width for a vertical menu, and it will fit across a wide range of devices while still leaving part of the page visible. Add the following CSS to your page:

While you are styling the menu you may want to remove the display: none and reverse the z-index settings so it will sit on top of the page. Once you’re happy with the design make sure you set them back so the page covers the menu entirely and it’s hidden until requested.

3. Avoiding the Browser Reflow

Now, at this point you might be thinking we can sprinkle some jQuery .slideIn() and .slideOut() magic on the menu and be done with it. Although that would work reasonably well on desktop browsers, it will perform poorly on mobile devices.

Using jQuery’s built in animation functions will animate the menu in a way that causes something called browser reflow. This is where the browser has to re-calculate and render the position of elements on your page each time something changes. If you’re animating your menu that’s a lot of frames and a lot of re-calculation, causing the animation to be sluggish on less powerful mobile devices.

There’s a way we can animate elements in the DOM and avoid causing reflow, it’s using a CSS property called transform.

4. CSS Transform and the GPU

Using the transform property will allow us to manipulate any element over three dimensions using the translate3d value. We can move the element on the X, Y and Z axis on the page. So for example to move the #page element 10px to the left, we could use:

Notice that we use a negative value to shift the #page element to the left. You need to think of the elements’ starting position always as 0, 0, 0 regardless of any other positioning applied to the element in your CSS.

It’s also important to include the vendor prefixed -webkit-transform version in your CSS for the best level of support across multiple browsers and versions. This will ensure your menu works on all modern desktop and mobile browsers to at least a few versions back.

CSS also provides the more axis specific transform options translateX() and translateY(), so why are we using translate3d()? The reason is we want to use hardware acceleration to eventually animate the transform. Using translate3d() allows us to access the power of the GPU (Graphics Processing Unit) on the mobile device to render the element and eventual animation. The GPU is designed for this sort of work so it can provide smoother animation than using the CPU alone.

When we apply translate3d() to an element, a GPU rendered copy of the element is created and placed on a separate layer from the rest of the page. This means any manipulation of that element will not cause browser reflow — we are only modifying that layer and not affecting any other elements on the page below. This dramatically improves the performance of animation on mobile devices.

So how can we make use of the CSS transform property to animate our menu? First of all we’ll need to track the state of the menu using JavaScript.

5. Add JavaScript to Track State

Using JavaScript to track the state of the menu will allow us to add CSS classes to the body element of the document. This will allow us to handle all of the animation purely in CSS.

If you take another look at the menu animation on this site you’ll notice that the menu is not actually moving. What is happening is the menu stays in one place — stuck to the top right edge — and the whole page on the layer above slides back to reveal it.

There are four distinct states:

  1. No animation, menu not visible
  2. Page animating to the left, revealing the menu
  3. No animation, menu is visible
  4. Page animating to the right, hiding the menu

We need to assign CSS classes to the body element to represent when we are in one of these four states. To do this you’ll need to add the following JavaScript to your footer (make sure you have jQuery loaded before this, or you can adapt it to raw JavaScript):

Now, with this JavaScript you’ll see the following body classes appear and disappear depending on the menu state:

  1. No animation, menu not visible — No classes on body
  2. Page animating to the left, revealing the menu — animating & left classes on body
  3. No animation, menu is visible — menu-visible class on body
  4. Page animating to the right, hiding the menu — animating & right classes on body

We’ll see why it’s important to know about these four different states and have distinct classes for each in the next step.

6. Let’s Animate!

From here on our all of the animation will be done using CSS transitions. We can now use the CSS classes that the JavaScript added to the body element to determine when to apply CSS transition properties to elements. Add the following CSS to your page right below the previous styles:

I think the first four declarations should hopefully make sense. The first is easy, the #menu element should not be hidden when animating or visible. The second makes sure that the #page element will have a transition applied to it when transformed. That basically means it will animate smoothly over .25 seconds to the transformed position rather than just jumping directly to it.

The next two declarations determine the direction and final position that the #page element should be transformed to. So if it’s .left then it goes 240px to the left, if it’s .right, then 240px to the right from its initial spot (remember it always starts at 0, 0, 0).

The final declaration is a little tougher. When the .animate, .left and .right classes are not present, which is when the menu is either fully visible, or fully hidden, none of the CSS transforms above will apply. That means that there’s no translate3d being set on #page. As soon as the animation classes are removed, and the .menu-visible class is added, #page is going to jump back to its original position at position: absolute; top: 0; right: 0. In order to stop this we need to adjust its position so it does not jump back after opening. We need to adjust it to right: 240px; to keep it open at 240px from the right edge of the page.

You might be thinking — why not just leave the .animating and .left classes on the body to keep the #page in the correct position? It’s true that this would retain the final position of the #page element after the transition. The issue is that it also leaves the element in a state of being rendered by the GPU since the translate3d() value still applies. This is a bad idea, ending up with too many elements being GPU rendered at once will cause mobile browsers to crash.

As a best practice you should only apply translate3d() when absolutely necessary during animation states. Use standard positioning to retain an elements’ final state after animating. This is the main reason we needed to track and apply CSS classes for the four distinct states.

This last CSS declaration will eliminate any flickering of elements while they are in a state of being animated, it only applies to webkit based browsers (most current mobile browsers). Add it below the CSS already added:

7. Make it Responsive (if needed)

You should now have a basic page with a sliding menu. With the code above the menu will always be present and working on any modern device, at any screen size. For it to only show on smaller mobile devices you’ll need to introduce a media query to your CSS. Here’s one that will apply to most tablets and smartphones:

You may want to be more granular with your media queries depending on the size and type of devices you want to support. The above is a fairly blunt approach and will keep the #menu and #toggle-menu elements hidden on bigger screens entirely. You may want to write some alternative styles for #menu on larger screens, or include an entirely separate menu inside of #page for larger screens that will be hidden on mobile devices.

That’s it! You should now have a smooth native-like slide in menu. Here’s a demo of the final version. There are many other great uses for CSS transforms, transitions, and animations, some of which I’ve covered in recent posts: JavaScript Pull to Refresh for the Web, and JavaScript Swipe Cards UX. With the right techniques it’s possible to replicate many of the buttery smooth animations you’d generally expect only in native apps.

I’ve packed up all of the code and put it on Github. You can use it as a base to adapt to your needs. I look forward to seeing some of your implementations, let me know if you can think of any good improvements to the code in the comments.

View on GitHub | Try a Demo

Published by Andy Peatling

Automattic Inc, open source contributor, and bogey golfer.

Join the Conversation


  1. Using touch start and click together can cause some issues like a toggle immediately firing twice. It’s better to remove click if you only need touch events. If you need to have both click and touch event then test for touch and apply either.

      1. They actually have a fairly robust solution for other issues that using touchstart causes like scrolling and accidental taps. In this situation though where the button is right at the top of the screen it’s not really a problem.

  2. Setting $body, $page and $menu variables is good practice but your code is doing this every time #toggle-menu is clicked. The same goes for setting the $page.on event handler, ie this handler does not need setting up every time #toggle-menu is clicked / touched. These items should be done outside of $( ‘#toggle-menu’ ).on(…)

  3. Your solution is among the best I have seen so far but I have one main issue that if you find a way to address it would make for the perfect sliding menu : scrolling in the menu is not independent from scrolling the whole page. Who cares right? The content is hidden anyway. Well there’s a big UX problem! Open the menu, scroll down as if the menu contained many items, now you just want to get back to your content… Scroll back up to find your way to the hamburger button. That’s not ideal.

    Two solutions :
    – find a way to scroll the menu independently (best)
    – fix the hamburger button position in place so it remains in view

    In any case, and as a bonus, make it so a touch on any portion of the remaining content area closes the menu.

    Do you think any of those or all are possible?

  4. Thanks Andy. This is a very nice tutorial. I also like how you make considerations towards animations and its effect on performance on older devices… especially because my I personally use devices/equipment that are all outdated, haha.

  5. One thing I wanted to add is that I think the mobile toggle/trigger could easily just be a item. Even though you have the prevent default in there, I did notice on my android 4.1.x browser that clicking it would refresh the page. With a , we don’t have to worry about any additional styling or functionality a browser may add. Also, adding cursor:pointer CSS can easily make it a desktop-friendly menu trigger as well.

  6. This is great!How can I make it to look such that menu appears on the content and doesnt push it?(so that I still see the “my New Website”).

  7. Andy, thanks for writing this article, and especially liked the discussion of hardware acceleration. Also, wanted to mention that the $page.on(…) with the $…) could be changed to utilize the $, function(e) { //all $body stuff }, and the $ could be removed.

    What will happen is when the transitionEnd event is fired, the event will be handled once. It simplifies the code a bit.

    Anyway, great job. I had always wondered how sites implement this feature.

Leave a comment

Leave a Reply