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!

Discussion (1)

Collapse
_collettesmith profile image
Collette

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