How to Build a Responsive Off-Canvas Menu With CSS and a Touch of JavaScript


In this tutorial, we’ll go through a simple yet effective method for creating a responsive off-canvas menu with HTML, CSS, and JavaScript.

To get an initial idea of what we’ll be building, take a look at the related CodePen demo (check out the larger version to see how the layout changes):

info

This tutorial won’t primarily focus on how to make the off-canvas menu fully accessible, so this would certainly be a valid next step to make it even more robust.

1. Begin With Markup

Our markup will consist of two wrapper elements:

  • the .top-banner element
  • the .top-nav element

Here’s the HTML code:

1
<section class="top-banner">
2
  <div class="top-banner-overlay">
3
    <!-- content here -->
4
  </div>
5
</section>
6
7
<nav class="top-nav">
8
  <div class="menu-wrapper">
9
    <!-- content here -->
10
  </div>
11
  <div class="fixed-menu">
12
    <!-- content here -->
13
  </div>
14
</nav>

2. Next We Need Some CSS

With the markup ready, next let’s examine the most important styles which are required for our menu.

info

For the sake of readability, this CSS code isn’t optimized—you’ll notice duplicate properties which you may want to strip out in your own version.

Styling the top-banner Element

The .top-banner element will look like this:

How the top-banner element looks likeHow the top-banner element looks likeHow the top-banner element looks like

Regarding its styles, we’ll do the following:

  • Set its width equal to the window width minus the width of the .fixed-menu element.
  • Set its height equal to the window height.
  • Define it as a flex container. This will force its overlay to cover the full parent height.
  • Use flexbox to vertically center the overlay’s content.

Here are the styles we’ll need to achieve all that: 

1
/*CUSTOM VARIABLES HERE*/
2
3
.top-banner {
4
  position: relative;
5
  left: 150px;
6
  display: flex;
7
  width: calc(100% - 150px);
8
  height: 100vh;
9
  background: url(IMAGE_PATH) no-repeat center / cover;
10
}
11
12
.top-banner-overlay {
13
  display: flex;
14
  flex-direction: column;
15
  justify-content: center;
16
  width: 350px;
17
  padding: 20px;
18
  transition: all .7s;
19
  color: var(--white);
20
  background: rgba(0, 0, 0, .7);
21
}

Styling the .top-nav Element

The .top-nav element will look like this:

How the top-nav element looks likeHow the top-nav element looks likeHow the top-nav element looks like

In this case, we’ll do the following:

  • Specify the direct child elements as fixed positioned elements that will cover the window height.
  • Use flexbox to vertically align the .fixed-menu element.
  • Hide the .menu-wrapper element by default. To do so, we won’t give it a property value such as display: none. In fact, we’ll use the translate() function to move it 200px to the left. Keep in mind that the element’s width is 350px, so part of it will still be within the viewport. However, it won’t be visible because the element is positioned underneath the .fixed-menu element.
  • Hide the menu list.

Have a look at the corresponding CSS styles below:

1
/*CUSTOM VARIABLES HERE*/
2
3
.top-nav .menu-wrapper {
4
  position: fixed;
5
  top: 0;
6
  left: 0;
7
  bottom: 0;
8
  width: 350px;
9
  padding: 20px;
10
  transform: translateX(-200px);
11
  transition: transform .7s;
12
  background: var(--menu-color);
13
}
14
15
.top-nav .menu-wrapper .menu {
16
  opacity: 0;
17
  transition: opacity .4s;
18
}
19
20
.top-nav .fixed-menu {
21
  position: fixed;
22
  top: 0;
23
  left: 0;
24
  bottom: 0;
25
  display: flex;
26
  flex-direction: column;
27
  width: 150px;
28
  padding: 20px;
29
  background: var(--fixed-menu-color);
30
}

3. Now for Some JavaScript

At this point, we’ll use some straightforward JavaScript code to manipulate the state of the off-canvas menu. 

Toggle Menu

We can toggle the menu in the following ways:

  • Each time we click on the .menu-toggle button, the menu’s visibility will change. If it’s hidden, it’ll appear with a nice slide-in effect, and the overlay will be pushed simultaneously to the right. Optionally, we can do a lot more things during this state. In our example, we’ll add a box shadow to the ::before pseudo-element of the overlay and reveal the menu list using a fade-in effect. On the contrary, if the menu is visible, it’ll disappear with a slide-out effect, and the overlay will be pushed simultaneously to the left.

  • Another possible way to close the menu is through the .menu-close button that is part of the .menu-wrapper element.
  • By default, we can also toggle the menu through the keyboard by focusing the toggle buttons and pressing the Enter or Tab keys. We’ll also add a scenario for closing the menu by pressing the Esc key. 

Here’s the JavaScript code which implements this behavior:

1
const topNav = document.querySelector(".top-nav");
2
const menuToggle = topNav.querySelector(".menu-toggle");
3
const menuClose = topNav.querySelector(".menu-close");
4
const menuWrapper = topNav.querySelector(".menu-wrapper");
5
const topBannerOverlay = document.querySelector(".top-banner-overlay");
6
const isOpenedClass = "is-opened";
7
const isMovedClass = "is-moved";
8
9
menuToggle.addEventListener("click", () => {
10
  menuWrapper.classList.toggle(isOpenedClass);
11
  topBannerOverlay.classList.toggle(isMovedClass);
12
});
13
14
menuClose.addEventListener("click", () => {
15
  menuWrapper.classList.remove(isOpenedClass);
16
  topBannerOverlay.classList.remove(isMovedClass);
17
});
18
19
document.addEventListener("keydown", (e) => {
20
  if (e.key == 'Escape' && menuWrapper.classList.contains(isOpenedClass)) {
21
    menuClose.click();
22
  }  
23
});

And below you’ll find the associated CSS styles:

1
.top-banner-overlay.is-moved {
2
  transform: translateX(350px);
3
}
4
5
.top-banner-overlay.is-moved::before {
6
  content: "";
7
  position: absolute;
8
  top: 0;
9
  bottom: 0;
10
  right: 100%;
11
  width: 20px;
12
  box-shadow: 3px 0 10px rgba(0, 0, 0, .75);
13
}
14
15
.top-nav .menu-wrapper.is-opened {
16
  transform: translateX(150px);
17
}
18
19
.top-nav .menu-wrapper.is-opened .menu {
20
  opacity: 1;
21
  transition-delay: .6s;
22
}

4. Going Responsive

At this point, we’ll examine how to make our off-canvas responsive.

When the viewport is up to 900px:

  • The top banner will have a height equal to the window height minus the height of the .fixed-menu element.
  • The top banner overlay will stop having the window height—it’ll be vertically centered inside the banner. 
  • Also, the fixed header will stop having the window height. In fact, it’ll have a fixed height of 50px.
  • The navigation layout will switch to horizontal.
  • The menu will be full-screen and continue to be off-screen by default.
  • Upon menu toggle click, the banner overlay will be moved off-screen to the right.

The page layout on screens up to 900pxThe page layout on screens up to 900pxThe page layout on screens up to 900px

The open menu stateThe open menu stateThe open menu state

Alternatively, keeping the banner overlay always on the screen and overlapping the menu while in view is also an option. Comment out the properties below marked with the number 2 and run the pen to see the difference.

As we’re using a desktop-first approach, these are the most important styles that we have to add/overwrite: 

1
@media (max-width: 900px) {
2
  body {
3
    font-size: 16px;
4
    overflow-x: hidden;
5
  }
6
7
  .top-banner {
8
    top: 50px;
9
    left: auto;
10
    width: 100%;
11
    height: calc(100vh - 50px);
12
    transform: none;
13
    align-items: center;
14
  }
15
  
16
  .top-banner-overlay {
17
    position: relative; /*2*/
18
    left: 0; /*2*/
19
    max-width: 100%;
20
  }
21
22
  .top-banner-overlay.is-moved {
23
    left: 100%; /*2*/
24
    transform: none;
25
  }
26
27
  .top-nav .menu-wrapper {
28
    width: 100%;
29
    transform: translateX(-100%);
30
    padding-top: 70px;
31
  }
32
33
  .top-nav .menu-wrapper.is-opened {
34
    transform: none;
35
  }
36
37
  .top-nav .menu-wrapper .menu-close {
38
    top: 70px;
39
  }
40
41
  .top-nav .fixed-menu {
42
    bottom: auto;
43
    flex-direction: row;
44
    align-items: center;
45
    width: 100%;
46
    height: 50px;
47
    padding: 0 20px;
48
  }
49
50
  .top-nav .fixed-menu .menu-toggle {
51
    margin: 0;
52
    order: 3;
53
  }
54
55
  .top-nav .socials {
56
    display: flex;
57
    margin-left: auto;
58
    margin-right: 20px;
59
  }
60
}

5. Clear Transitions on Window Resize

We’re almost done! Similar to what we’ve done in another off-canvas tutorial, let’s make one more adjustment: we’ll clear all transitions each time we resize the browser window. This way, during the resizing phase, we’ll avoid seeing the off-canvas for a moment before moving back to its default off-screen position.

Here’s the extra code that we’ll need:

1
const body = document.body;
2
const noTransitionClass = "no-transition";
3
let resize;
4
5
window.addEventListener("resize", () => {
6
  body.classList.add(noTransitionClass);
7
  clearTimeout(resize);
8
  resize = setTimeout(() => {
9
    body.classList.remove(noTransitionClass);
10
  }, 500);
11
});

And the related style:

1
.no-transition * {
2
  transition: none !important;
3
}

Conclusion

That’s it, folks! We managed to build a useful off-canvas menu that will appear nicely on different screens with relatively straightforward code. You can build on the demo and make it more accessible by adding extra ARIA attributes like the aria-labelledby attribute and the dialog role.

I hope you enjoyed the final result and you’ll use it as inspiration for creating even more powerful off-canvas menus. And of course, if you build any, don’t forget to share them with us!

Learn More Off-Canvas Techniques



Source link