Performance & RWD Implementing Off-Canvas Navigation For A Responsive Website


The varying viewports that our websites encounter on a daily basis continue to demand more from responsive design. Not only must we continue to tackle the issues of content choreography1 — the art of maintaining order and context throughout the chaotic ebb and flow of the Web browser — but we must also meet the expectations of users. They’re not sitting still.

With the likes of Firefox OS2 (Boot to Gecko), Chrome OS3 and now Ubuntu for phones4 — an OS that makes “Web apps” first-class citizens — delivering native app-like experiences on the Web may become a necessity if users begin to expect it. Many in our field have argued for a degree of separation between the Web and native platforms for both technical and philosophical reasons. They’re certainly wise to heed caution, but as consumer devices continue to blur the boundaries, it’s worth thinking about what we can learn from native app design.

A Demonstration

In this article, I’ll be walking through a build demo that centers on two topics. The first is responsive design patterns5 that embrace the viewport and that improve content discoverability beyond the basic hyperlink; in this case, off-canvas navigation. The second is the complexities of implementing such ideas in an accessible and highly performant manner. These are two topics that I believe are at the heart of the Web’s future.

With that in mind, let’s get building.

The Accessible Base

All good things begin with a solid foundation of semantic HTML and widely supported CSS. In theory, this baseline should function as a usable experience for all browsers that visit our website. (It might also be the final experience in less-capable browsers.)

As a starting point, I’ll use a technique very similar to Aaron Gustafson’s “Smart Mobile Navigation Without Hacks6.” It requires no JavaScript to function.

Responsive Off-Canvas Menu Demo 17
Step 1 of the responsive off-canvas menu. Short link:

  • View demo 19
    Be sure to view on a mobile or small screen, and take a while to inspect the code. Although our final design will be significantly different, starting simple is vital; retrofitting accessibility isn’t trivial.

The HTML body looks like this (I’ve stripped a few attributes for semantic clarity):

<header id="top" role="banner">
    <h1>Book Title</h1>
    <a href="#nav">Book navigation</a>
<nav id="nav" role="navigation">
        <li><a href="#">Chapter 1</a></li>
        <li><a href="#">Chapter 2</a></li>
        <li><a href="#">Chapter 3</a></li>
        <li><a href="#">Chapter 4</a></li>
        <li><a href="#">Chapter 5</a></li>
    <a href="#top">Return to content</a>
<article role="main">
    <!-- [main content here] -->

You could consider the HTML alone, with little to no styling, as being “breakpoint zero.” If it’s not logical at this stage, then accessibility will not improve.

Demo 1 Breakdown

  • Media queries are based on a viewport width of 45em (that’s content-dependent). Above this breakpoint, the navigation is permanently visible. I prefer em units because they allow breakpoints to maintain a relationship with text size. Lyza Gardner explains in detail in her post “The EMs Have It: Proportional Media Queries FTW!10
  • I’m using both min-width and max-width media queries to scope CSS. This adds a bit of complexity. Most people prefer a “mobile-first” build, using only progressively larger min-width queries. The downside with that technique is the amount of resetting required if an element has noticeably different visual states. Neither method is right or wrong.
  • The crux of this initial stage is the :target pseudo-class11 selector, utilized to show and hide the navigation. Only IE8 and lower lack support. However, this is a non-issue if you serve a semi-fluid desktop style sheet to old IEs. Jake Archibald12Nicolas Gallagher13 and Stuart Robson14 can tell you more.

As the demo takes shape, I’ll continue to introduce the main development principles. There’s a long way to go yet…

Going Off-Canvas

For some websites, the above may suffice — but not for us! We’re experimenting with off-canvas patterns and striving for that native experience. Because we cannot ignore older browsers, it’s now time to progressively enhance.

Responsive Off-Canvas Menu Demo 215
Step 2 of the responsive off-canvas menu. Short link:

  • View demo 217
    You will see the restyled navigation with basic functionality.

Demo 2 Breakdown

  • I’m adding the class js-ready to the document element after the DOMContentLoaded18 event fires. The selector .js-ready is used as a hook to safely restyle the navigation off-canvas. If for whatever reason JavaScript doesn’t load, then the original functionality from demo 1 still exists.
  • To show and hide the navigation, I’m toggling a class of js-nav on the document element when the user clicks (or taps) the relevant buttons. This simply applies a style of left: 70% to the #inner-wrap element (#outer-wrap is used to hide any overflow and to avoid scrollbars).

This is a fairly basic enhancement, but importantly it remains usable before JavaScript is ready. It’s also notable that no inline styles are written with JavaScript; only classes are used to manage states.

Jumping between open and closed navigation states makes for a jarring user experience. Users need to understand — or even see — how an interface has changed. This is often the point where developers let the Web down. To be fair, building user interfaces is incredibly difficult. What I’m going to show below is far from perfect, but it’s certainly a step in the right direction.

So, we have the set-up. Now let’s add transitions.

Transitioning (The Wrong Way)

I’ll start by getting it all wrong, because this is how I would have done it a few years ago, and learning from mistakes is important.

Responsive Off-Canvas Menu Demo 3 (jQuery)19
The responsive off-canvas menu using jQuery .animate for transitions. Short link:

jQuery21 is a resource that many front-end developers begin with to learn JavaScript. Personally, I am a big fan (never blame the tools), but unfortunately jQuery has a habit of making things look deceptively simple. It masks complexity and, with that, understanding.

Transitioning our off-canvas navigation with jQuery is very easy:

$('#nav-open-btn').on('click', function() {
    $('#inner-wrap').animate({ left: '70%' }, 500);

$('#nav-close-btn').on('click', function() {
    $('#inner-wrap').animate({ left: '0' }, 500);

Visually, this achieves the effect we’re after, but test it on mobile and watch the frame rate stutter. Performance is dreadful.

This method is bad for several reasons:

An animation of jQuery updating the DOM

  • jQuery’s .animate increments the element’s style attribute on every animation frame (as can be seen in the GIF above). This forces the browser to recalculate the layout22.
  • It leaves inline styles in the DOM that have very high specificity and that will override our well-maintained CSS. This is a big issue if the viewport is resized and triggers different breakpoints.
  • A separation of concerns is lost because styles are defined in JavaScript files.

Overall, it’s a performance and maintainability nightmare. There is a better way.

Transitioning With CSS

Putting the jQuery experiment aside, I’m now building from the second demo again, this time using CSS transforms and transitions. These enable us to smoothly animate the off-canvas navigation with great performance.

Responsive Off-Canvas Menu Demo 423
The final responsive off-canvas menu using CSS transforms and transitions. Short link:

  • View final demo25
    Performance has drastically improved compared to the jQuery example.

Final Demo Breakdown

  • Returning to CSS, I’m once again using the .js-nav class to toggle the navigation, while making no actual style alterations with JavaScript.
  • I’m progressively enhancing with the classes .csstransforms3d and .csstransitions (applied to the document element by Modernizr26).
  • Instead of moving the navigation with negative positioning (left: -100%), I’m using the transform property: transform: translate3d(-100%, 0, 0).
  • CSS transitions are used to animate transform changes: transition: transform 500ms ease. And I’ve added a few more transforms to enhance the visual effect.

With the help of Modernizr, this will fall back to demo 227 if browser support is lacking for CSS transforms and transitions. (In theory, I could fall back to jQuery animation, but it’s really not worth it.) When you download Modernizr28, you can include only the feature detection that you need. It also includes the HTML5 shiv for IE. All in all, it’s a very useful script.

The benefits here are immense. First and foremost, by transitioning elements with 3-D transforms, the browser can generate render layers that are hardware-accelerated29. Secondly, there’s no need for JavaScript to worry about style or media-query breakpoints. JavaScript need only interpret user interaction and apply classes to maintain states — CSS defines the visual changes.

We can look deeper into performance by using Chrome’s Developer Tools. The results below are from the desktop browser, but for mobile performance you could use USB Remote Debugging30 on Android devices.

jQuery Animation Performance

jQuery animation performance in Chrome Developer Tools31

The four events above represent the opening and closing of the navigation twice. In the diagram, yellow represents the JavaScript running, purple is the rendering (recalculating the style and layout), and green is the painting to the screen. On mobile, we’d be shooting way below that 30 FPS line. I also tested on an iPhone 3GS and could literally count 3 FPS with my own eyes — it’s that slow!

CSS Transition Performance

CSS transition performance in Chrome Developer Tools32

What a difference! The only JavaScript that exists is there to manage user interaction before and after the transition. The green we’re seeing is the minimum that the browser needs to repaint the transition at a respectable frame rate, using GPU acceleration33.

Possible Concerns

As with all new Web standards, nothing is inherently perfect.

In WebKit-based browsers, the font smoothing34 may switch to antialiased from the default subpixel-antialiased when CSS transforms or transitions are applied. This could result in visually thinner text, which many designers actually prefer — but not something you should “fix.”35

Flickering can also occur if an element goes between transform and no-transform. To avoid this, always start with a default like translate3d(0,0,0) on an element that will move later, so that the render layer is composed and ready.

The Next Step

Enhancing further, we could detect and take advantage of touch gestures, like swiping, to really bring this implementation closer to par with its native counterparts — “closer” being the operative word, but I do believe the gap is closer than many would have us believe.

It’s also — in my opinion — a gap that we need to bridge.

There are other clever ways to increase interaction speed. Mobile browsers tend to wait around 300 ms to fire click events. Google’s Ryan Fioravanti has a great article on “Creating Fast Buttons36” to reduce this latency.

Transition easing37 can radically change the visual effect and the user’s perception of what’s happening. Whether elements need to spring, bounce or accelerate into action, Lea Verou has a useful cubic bezier38 resource to generate custom easing functions.

Hopefully, this article has shown the improvements that can be made if we take extra care to understand the technology we’re using.

Download The Code

So, there we have it: a three-tiered responsive, interactive, off-canvas menu. I’ve set up a GitHub repository39 where you can view all the code. Have a play with it; there are a few bits and pieces I haven’t covered to avoid bloating this article.

Further Reading

To really understand why the techniques highlighted above are my preferred solution, I point you in the direction of these resources:



  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46

↑ Back to top Tweet itShare on Facebook

David Bushell is a website designer and front-end developer working at Browser Creative, London. He blogs regularly at and xheight, and shares inspiration and web design related interests at Design Heroes. You can also follow him on Twitter.

  1. 1

    That’s an absolutely amazing solution – but i wonder : would it work with a multi-level menu, with nested lists ? Would there be any technical limitation preventing that approach to work ?

  2. 52

    Thanks for this — very handy indeed. But…

    I’m running into trouble when combining this with an embedded Google Map on the body of the page. The map takes up most of the width and height of the page.

    When I close the menu, the area that it took up (70% width in your examples) is no longer draggable on the map. So, I can drag the map on the right of the page, but not on the left.

    Manually resizing the window fixes this on the desktop but I’m having no luck in mobile browsers.

    Thanks for any help that you can give.


  3. 103

    love the article, but I am having difficulty getting this to work properly on my wordpress site.

    Could you please have a look and tell me what I have done wrong.

    The nav is appearing ok, but when you click on the mobile version to show the menu, it is appearing on top of the page rather than off canvas ?

    Many thanks


  4. 154

    Any suggestions on clearing the divs if e.g. the off canvas ‘column’ is higher than the content area? The classical approach ( ) doesn’t work with off canvas

  5. 256

    Can anybody help me how to get the footer stick to the bottom with this example? Tried a lot of things, but nothing worked!!

  6. 307

    Great article! very nice, Thanks.

  7. 358

    Odd, if I have an element inside #inner-wrap with position:fixed, it will scroll with the page.

  8. 409

    any reason why this should not work in wordpress? your demo works fine on its own but in wordpress i get errors (Uncaught TypeError: Cannot read property ‘transitionDuration’ of null)

  9. 460

    Hi David,

    You done great job, i loved it. I need your help in following issue, I implemented your idea and its working fine in browsers but in ipod 5, when the side navigation close, the tab navigation is jurking(it shakes onces). could you please let me know how to over come.

  10. 511

    It is a great way to make responsive menu. But, what to do if I have a drop-down menu? How can I make that responsive using this method?

  11. 562

    To the author: thanks for sharing your code. But what precisely are you doing? Where are the instructions of what we need to do in *our* code, which may not follow the HTML directives that your example does? All we need to know is how to:

    1. Include the necessary classes, etc, from Modernizr.

    2. How to tag our NAV elements with the appropriate class names etc. I am using Foundation 4. Will your code work with Foundation based layouts?

    3. What JS to include? In your example, it would be nice if you took out all the junk code. No need for the content samples etc. All we need is the menu bar. It’s presently hard to extract which precise bits of CSS/JS are being used. Because it relies heavily on specific class names (such as “inner-wrap”) which shouldn’t be needed really…you can achieve the same via the BODY tag?

    It would be nice to understand what needs to happen. Until the point where the whole nav is converted into a small “menu” icon (the hamburger icon with three lines), I’ve got it. On desktop my nav menu shows up as a list. On mobile, it shows up with the burger icon. Now how to make sure that the mobile icon, when clicked, brings out the menu as in your example?

    The article is a promising one, but doesn’t contain details. Your demo is fantastic, but it would be nice to have specific steps without the needless html.

    Thanks so much!

    (Smashing Magazine doesn’t have a “Notify when someone replies” feature, so please email me an alert if you can Thanks)

    • 613

      I had the same issue… very confused on how exactly to create the button that display and hides the nav. Please explain.

  12. 664

    It’s a great example, one of best ones available I think. Thank you! I just don’t understand the need in monitoring the “transitionend” event in javascript part, isn’t it enough to just toggle the “js-nav” class?

  13. 715

    Any way of implementing a left and right off canvas menu?

  14. 766

    Is there a download available for your first example, except with the smooth transitions applied like in the final one? I prefer a vertical dropdown menu rather than a horizontal side pull-out navigation.

  15. 817

    Nice tutorial, so thanks!
    But I wonder how #top and #nav was removed from the url. Can you help me?
    Thank you!

  16. 868

    David, does this off-canvas solution work with turbolinks in a Rails app? I’ve tried using the Besite off-canvas solution from but it seems to be overkill for what i want and doesn’t seem to play well with turbolinks.

  17. 919

    Awesome article, working on implementing solution 4.

    I’ve tested on my iPhone and I can really tell the 3 millisecond delay. I’ve tried several scripts out there (FastClick,’s and PhoneGap’s script) and have been unable to remove this 3ms delay on the tap.

    Any idea how to remove it?


  18. 1021

    can i use this for two section in same page

  19. 1072

    I’ve found a problem with this menu code after implementing it. On pages with very little content, the page is not long enough and the slide out menu gets cut off.

    A few pages of my site have only a short paragraph of content and the navigation is 20 or so items and the lower items cannot be accessed. I downloaded the demo files again and tested it by removing most of the Treasure Island text and putting in many more nav items and it is broken there too…any help on fixing this? Something to do with the inner and outerwrap….

  20. 1123

    David Pearson

    May 2, 2014 2:20 pm

    Does anyone else have a problem with this for Android browsers?

    The push works but the links themselves appear to pushed right underneath the main body…? Leaving you with an empty menu bar.

    • 1174

      I do experience the same issue…any updates on it?

    • 1225

      I fixed it with:
      z-index: 1000000;
      display: block;
      opacity: 1;
      filter: alpha(opacity=100);

      Try putting this on the the main div holding the navigation Items

  21. 1276

    The “step4.html” version does not work 100% correctly when tested on an iPhone. The menu toggles fine when tested in profile and landscape. However, if opening and closing the menu in landscape, then switching to profile, a long black bar appears in profile that will not go away unless you toggle the menu again in profile.

    Not sure why this is occurring, but a solution would be nice.

  22. 1327

    I’m wondering about the code you’re consequently using to hide stuff, for instance here:

    Where does this originate? It looks somewhat like this stuff from Snook, except you added margin/border/padding?

    From what I’m reading, this is a way to hide content but still leave it accessible?

  23. 1378

    I am enjoying the article, and somewhat stuck in the step-one demo. So in the page I have made it all works fine, except when you click the “.nav-btn” the page scrolls/jumps down below the header to “#nav”, header then being out of view. What I have been trying to figure out is how your demo dosent do this. What am I missing here. What is the part that makes that jump, … not jump?

  24. 1429

    An Off-Canvas Checkout, why not? :)
    We do follow the trending off-canvas to build off-canvas checkout for Magento user.
    Have a look
    Please leave your comment for this module ^^

    • 1480

      Awesome Calvin, It’s the most impressive one yet! We have a client we are going to start talking to about using this module. I can see this one becoming very popular. Great work!

  25. 1531

    Thank you for such a great article! I’m implementing the CSS only version on my site’s sticky header and it works fine except that every time you click the “trigger button” the side menu opens just fine, but the wrapper div with the my content on it jumps to the top, which forces the viewer to lose his/her place on the page if they choose to go back to viewing it.
    Any idea for fix?

  26. 1582

    This part of code in main.js doesn’t work on a smartphone (tested on Iphone6)

    // close nav by touching the partial off-screen content
    document.addEventListener(‘click’, function(e)
    if (nav_open && !hasParent(, ‘nav’)) {

    Anyone already some solution? If I find it here, I update.

  27. 1633

    Hello, for all those who had question about menu and how it works how to simplify and just with jQuery trigger + css transitions

    resize (default menu on desktop not style – left for you :) )

  28. 1684

    Really love the project, thanks for sharing. Stokesga implemented the off canvas state fulltime with his Stalledtime site qiute early on in your comments above. Obviously he’s more technically savvy than myself as I’ve been tinkering for ages now and still seem no closer to a fullscreen hidden nav. Could you give me and other readers some pointers on where to begin to show only the “hamburger” nav icon all the time?

    Cheers again

  29. 1735

    The “step4.html” version does not work when tested on an iPhone. The menu toggles fine when tested in profile and landscape. However, if opening and closing the menu in landscape, then switching to profile, a long black bar appears in profile that will not go away unless you toggle the menu again in profile.
    Can you help to fix it? please help me ,thanks.

  30. 1786

    Good evening.
    Use of these problems are discovered and leave a comment.

    Address #name enters discarded hiding behind a header from the responsive web.

    How can we be modified?

    This article was written by the translator.

  31. 1837

    Currently working on

  32. 1888

    Nathaniel Flick

    March 23, 2015 3:20 am

    I’ve discovered an error in your code on mobile, repeat the following steps:

    1. Select “View Final Demo” on this page
    2. Turn iphone or ipad to landscape
    3. Open and close the menu
    4. Turn iphone or ipad to portrait and left sidebar/black comes up over content instead of being hidden

    Is there a quick fix for this?

  33. 1939

    I have used this technique for the off canvas menu , then i tried to put in position fixed, but in FF it does not show up. I have opened a question in stackoverflow explaining step by step the problem

  34. 1990

    Instead of using `overflow: hidden` on `outer-wrap`, you could just put `overflow-x: hidden` on the body when your document is `js-ready`.

  35. 2041

    Gorgeous, clean and handy code. Thanks for sharing.

  36. 2092


    Thanks for a great tutorial but none of them work in internet explorer. Not even step 1. Works in every other browser. I have the same problem with another mobile menu I made using a j.s prepend.

    Any ideas. It seems odd that none of them work in I.E. Not even your examples on your site.???


  37. 2143

    An Off-Canvas Checkout, why not? :)
    We do follow the trending off-canvas to build off-canvas checkout for Magento user.
    Have a look
    Please leave your comment for this module ^^


Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top