DEV Community

Cover image for Drawer navigation menu using CSS and Vue JS
debadeepsen
debadeepsen

Posted on

Drawer navigation menu using CSS and Vue JS

One of the things that I have found impressive in mobile apps is the drawer that opens from the side (left or right) which typically contains navigation links. The same behavior is replicated in many websites, not just for menu, but in some cases to display other things like a list of search filters. Recently, I had to implement this for a Vue JS project I was working on. There are many npm packages for it, but I eventually decided to make it myself, because I could design it exactly the way I liked it, and it would also give me a chance to learn about the inner workings of such prebuilt packages. Turns out, it's pretty simple - here's how I did it.

Assumptions

For the purpose of this article, I will assume that

  1. you are familiar with Vue JS and Vue CLI
  2. you have a basic understanding of CSS

Project setup

I created a Vue project using Vue CLI, and went to the .vue file where I wanted the menu to be. I also added some content and basic css to make it look reasonably pretty.

<template> <div> <div style="text-align:right"> <button class="toggle"><i class="las la-bars"></i> Show Menu</button> </div> <h1>Welcome to Vue JS</h1> <h3>This is a sample page with some sample content</h3> <p> Alone during the day, in my room or out of doors, I thought аbout the waiter more than about my раrеnts; as I now realize, it was а kind of love. I had nо desire for contact, I wanted only to bе near him, and I missed him on his day off. When he finally reappeared, his black-and­-white attire brought lifе into the rооm and I acquired а sense of color. Не always kept his distance, even when off duty, and that may have accounted for my affection. Оnе day I ran into him in his street clothes at the bus-station buffet, now in the role of а guest, and there was no difference between the waiter at the hotel and the young man in the gray suit with а raincoat over his аrm, resting оnе foot on the railing and slowly munching а sausage while watching the departing buses. And perhaps this aloofness in combination with his attentiveness аnd poise were the components of the beauty that so moved me. Even today, in а predicament, I think about that waiter’s poise; it doesn’t usually help much, but it brings back his image, and for the moment at least I regain my composure. </p> <p> Тoward midnight, оn my last day in the Black Earth Hotel – all the guests and the cook, too, had left – I passed the open kitchen on my way to my room аnd saw the waiter sitting bу а tub full of dishes, using а tablecloth to dry them. Later, when I looked out of my window, he was standing in his shirtsleeves on the bridge across the torrent, holding а pile of dishes under his right аrm. With his left hand, he took one after another and with а smooth graceful movement sent them sailing into the water like so many Frisbees. </p> <p> From <a target="_blank" href="https://www.nobelprize.org/prizes/literature/2019/handke/prose/" >https://www.nobelprize.org/prizes/literature/2019/handke/prose/</a > </p> </div> </template> 
Enter fullscreen mode Exit fullscreen mode

Next, we'll add the div that'll contain the menu, and the mask that appears over the page content when the menu is open. I'm keeping it fairly simple.

 <div class="right-drawer"> <h1>Menu</h1> <h4>Home</h4> <h4>About</h4> <h4>Stories</h4> <h4>Testimonials</h4> <h4>Contact</h4> </div> <div class="drawer-mask"></div> 
Enter fullscreen mode Exit fullscreen mode

Now, the CSS for it. We'll position both of these absolutely. Intially, the drawer div will have its width set to zero. When we click a button to open our menu, we'll increase its width gradually through a CSS transition, and do the same with the opacity of the mask. When the menu is closed, we'll do the opposite. We'll also run the padding through the same transitions, to make sure no part of the drawer is "peeking out" in its closed state.

.right-drawer { position: absolute; top: 0; right: 0; width: 0; /* initially */ overflow: hidden; height: 100vh; padding-left: 0; /* initially */ border-left: 1px solid whitesmoke; background: white; z-index: 200; } .drawer-mask { position: absolute; left: 0; top: 0; width: 0; /* initially */ height: 100vh; background: #000; opacity: 0.3; z-index: 199; } 
Enter fullscreen mode Exit fullscreen mode

vw and vh stand for view width and view height respectively. They're handy units that you can use to quickly set the dimensions of elements relative to the full screen width or height.

Showing the drawer

Now for the interactions. Once again, extremely simple. We'll add a state variable called drawerVisible, which will control the opened and closed state of the drawer.

<script> export default { data() { return { drawerVisible: false, }; }, }; </script> 
Enter fullscreen mode Exit fullscreen mode

We'll modify the CSS for the drawer by adding a transition:

.right-drawer { position: absolute; top: 0; right: 0; width: 0; /* initially */ overflow: hidden; height: 100vh; padding-left: 0; /* initially */ border-left: 1px solid whitesmoke; background: white; z-index: 200; transition: all 0.2s; /* for the animation */ } 
Enter fullscreen mode Exit fullscreen mode

And we'll add a style to the drawer div, to make it behave in accordance with the value of the state variable drawerVisible.

<div class="right-drawer" :style="{ width: drawerVisible? '20vw' : '0', paddingLeft: drawerVisible? '10px' : '0', }" > ... 
Enter fullscreen mode Exit fullscreen mode

Remember that when you use dynamic styles in Vue, you are required to bind it to a JavaScript object, unlike how you would have kept its value as a string if it was static.

Lastly, let's attach an event handler to the click event of the "Show Menu" button:

<button class="toggle" @click="drawerVisible = true"> <i class="las la-bars"></i> Show Menu </button> 
Enter fullscreen mode Exit fullscreen mode

If you've got this far, the drawer should now be working. However, there is another part remaining - to display a translucent mask over the main content while the drawer is up. For that, we just need to alter the dimension and opacity of the mask, as the value of drawerVisible changes.

<!-- We will make the mask fill the screen while the menu is visible. Because its z-index is 1 less than that of the menu, the menu will still be displayed on top of it --> <div class="drawer-mask" :style="{ width: drawerVisible ? '100vw' : '0', opacity: drawerVisible ? '0.6' : '0', }" ></div> 
Enter fullscreen mode Exit fullscreen mode

Hiding the drawer

We're almost there. All we need now is a way to close the drawer. We'll implement two of them, in fact - a close button inside the drawer, and also allowing the user to close it by clicking on the mask. For the first, add a button inside the drawer like so -

<div style="text-align:right; margin:5px"> <button class="close" @click="drawerVisible = false"> <i class="las la-times" style="font-size:24px"></i> </button> </div> 
Enter fullscreen mode Exit fullscreen mode

And simply add a click event handler to the mask will do the trick for the other one.

<div class="drawer-mask" :style="{ width: drawerVisible ? '100vw' : '0', opacity: drawerVisible ? '0.6' : '0', }" @click="drawerVisible = false" > </div> 
Enter fullscreen mode Exit fullscreen mode

The scrollbars are a problem, especially if you scroll down, because the mask doesn't cover the lower part. That can be addressed by changing the positioning styles of the drawer and the mask, or by turning the scrollbars off, by setting the overflow property of the body tag to hidden.

That's it! Here's the full code running on CodeSandbox. Feel free to ask me questions in the comments!

Top comments (2)

Collapse
 
bishop1988 profile image
Michael Bishop

Thanks for the post, this helped me out

Collapse
 
_collettesmith profile image
Collette

How would i make this reactive so it can be accessed throught