Vue Tutorial How to make a responsive burger menu

A Hamburger menu will help if you have a big menu on your site, it is always recommended that you show pages you want to drive traffic to, like your blog. But sometimes there are a lot of pages on your website, so adding a hamburger menu can help save screen real estate, and it is universally recognized. If you want to add one to your website then you are in the right place.
Today, I will show you how you can add one using Vuejs. The first thing we are going to do is create a component to hold our hamburger code. By adding everything inside of a component you make sure that you can reuse this code for other purposes and it will be cleaner for your code.

<template>
    <div id="burger">
        <button type="button" class="burger-button" title="Menu">
            <span class="burger-bar burger-bar--1"></span>
            <span class="burger-bar burger-bar--2"></span>
            <span class="burger-bar burger-bar--3"></span>
        </button>
    </div>
</template>

<script type="text/javascript">
    export default {
        name: 'Burger'
    }
</script>
<style scoped lang="scss">

</style>

Adding the HTML

The HTML for it in Vue is fairly simple. We need a simple parent div, in this case with an ID of burger. This element needs a class for the Dom to know when it is active or inactive, for that, I am going to bind a class using due like :class=“{}”.

This allows us to pass the selector we want and then as the value is a true or false statement, if it’s true the css element will be added, if not then it will not add the class. In this case it will :class=”{   ‘active’ : active}”. Keep in mind that in due you can do the same with style.

<template>
    <div id="burger" :class="{
        'active': active
    }">
        <button type="button" class="burger-button" title="Menu">
            <span class="burger-bar burger-bar--1"></span>
            <span class="burger-bar burger-bar--2"></span>
            <span class="burger-bar burger-bar--3"></span>
        </button>
    </div>
</template>

Active will have the state for the menu is active, I am going to add this as a property, so I can manage the state from the parent component. And we are then adding an action when the user clicks to taps the button to toggle the active state of the button, this will emit an event, and the parent component will pick this event up to change the state active of the menu.

<template>
    <div id="burger" :class="{
        'active': active
    }" @click="toggleActive">
        <button type="button" class="burger-button" title="Menu">
            <span class="burger-bar burger-bar--1"></span>
            <span class="burger-bar burger-bar--2"></span>
            <span class="burger-bar burger-bar--3"></span>
        </button>
    </div>
</template>

<script>
    export default {
        name: 'Burger',
        props: {
            active: {
                type: Boolean,
                required: true,
                default: false
            }
        },
        methods: {
            toggleActive () {
                this.$emit('toggle-menu')
            }
        }
    }
</script>

Adding the CSS

When I needed to add the menu to my client’s site, I looked everywhere and ended up combining different styles and modifying them. As you can see in the result each burger bar has an independent transition to give it a nice effect.

In this case, I use The cubic-bezier option in CSS. As these curves are continuous, they are often used to smooth down the start and end of the animation. You can read more about it here https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function

There is an independent cubic-bezier for different styles, one for the transform, one for the background color one for the opacity, together they allow to create the nice animation you see in the hamburger menu. The transition between it being a hamburger menu and then a close icon (an x).

<style scoped lang="scss">
    $burger-color: #444;
    $primary: #2b2b68;

    .hidden {
        visibility: hidden;
    }

    button {
        cursor: pointer;
    }

    /* remove blue outline */
    button:focus {
        outline: 0;
    }

    .burger-button {
        position: relative;
        height: 30px;
        width: 40px;
        display: block;
        z-index: 99;
        border: 0;
        border-radius: 0;
        background-color: transparent;
        pointer-events: all;
        transition: transform .6s cubic-bezier(.165, .84, .44, 1);
    }

    .burger-bar {
        background-color: $burger-color;
        position: absolute;
        top: 50%;
        right: 6px;
        left: 6px;
        height: 3px;
        width: auto;
        margin-top: -1px;
        transition: transform .6s cubic-bezier(.165, .84, .44, 1), opacity .3s cubic-bezier(.165, .84, .44, 1), background-color .6s cubic-bezier(.165, .84, .44, 1);
    }

    .burger-bar--1 {
        -webkit-transform: translateY(-6px);
        transform: translateY(-6px);
        top: 40%;
    }

    .burger-bar--2 {
        transform-origin: 100% 50%;
        transform: scaleX(1);
    }

    .burger-button:hover .burger-bar--2 {
        transform: scaleX(1);
    }

    .no-touchevents .burger-bar--2:hover {
        transform: scaleX(1);
    }

    .burger-bar--3 {
        transform: translateY(6px);
        top: 60%;
    }

    #burger.active .burger-button {
        transform: rotate(-180deg);
    }

    #burger.active .burger-bar {
        background-color: lighten($primary, 10);
    }

    #burger.active .burger-bar--1 {
        transform: rotate(45deg);
        top: 50%;
    }

    #burger.active .burger-bar--2 {
        opacity: 0;
    }

    #burger.active .burger-bar--3 {
        transform: rotate(-45deg);
        top: 50%;
    }

    @media screen and (max-width: 991px) {
        #burger {
            display: block;
        }
    }

    @media screen and (min-width: 990px) {
        #burger {
            display: none;
        }
    }
</style>

Changing the state from the parent component

Now, its time to actually make this toggle,  the state for the active menu is on the parent component of burger, In my case I am using Laravel, so my parent will be the Vue instance itself, you may create a component for your menu as your parent component, in part 2 of the video tutorial I go through the pros and cons of this.

In the parent we can copy a menu from the Bootstrap 4 website, if you don’t have a menu already, the main point here is that you need to add the burger component somewhere near the menu, in the Bootstrap 4 case, the toggle is right above the menu, so I will just replace it with the Birger menu we are now passing or state to the burger component, the child as property. Also, this is changing the menu active to its opposite when some clicks or taps the burger menu.

<burger-menu @toggle-menu="menuActive = !menuActive" :active="menuActive"></burger-menu>

The rest its the bootstrap default navigation, I will add a v-show to display none when menuActive is false. This will allow you to control whether you show the menu or not. This is only going on any mobile device or tablet. And we will go over that in a little bit.

<div id="navbarNav" v-show="menuActive">
  <div class="col-xl-6 offset-xl-6">
    <ul class="navbar-nav float-xl-right">
      <li class="nav-item active">
        <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">Features</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">Pricing</a>
      </li>
      <li class="nav-item">
        <a class="nav-link disabled" href="#">Disabled</a>
      </li>
    </ul>
  </div>
</div>

Setting the state in Vue

It is now time to set up the state in our data object in the due instance, in this case, the parent for burger. I am going to set the key as menuActive, and then the value will be if it’s mobile then false and if it’s desktop then true.

The way I know it’s by getting the inner width of the window. You can do it like window.innerWidth > 991, so when the page loads if it’s mobile it will hide the menu, and if it’s desktop then it will show the menu. 

This will complete the work with the default due to animations, they don’t slide or do anything just shows and hides. If you want to add animation then continue reading, I will add animation using Velocity JS

Animating the menu

Vue has a transition mechanism that we can take advantage of the transitions. You can read more about it right here https://vuejs.org/v2/guide/transitions.html  

First, we need to add a transition wrapper around the menu, make sure it wraps the element that has the v-show or v-if in it. Once you do that, Vue knows that this has a transition. For this animation, I am going to use Javascript hooks.

The Javascript hooks allow us to specify the transitions that we want at different stages of the transition, in this case, we are going to use the enter and leave hooks, and then the before-enter hook for the overlay. The way we can do this is by binding an event to the transition element.  [Transition code with the hooks]

Next, we have to add the methods that the hooks are referring to, we are going to go to the parent of the Birger for this, in my case the due instance itself, but you can use the parent component if that is what you have. 

One more state that I am going to set up for the overlay, in our case it will be globalOverlay.active. This way I can show or hide the overlay on the background so that users focus on the menu and not anything else.

Back to the methods, on the beforeEnter method, we will display the overlay, on the enter method, we will use Velocity to make the margin-left 0 and on the leave method, we will use Velocity to make the margin-left -100%.

beforeEnter () {
    if (this.isMobile) {
        this.globalOverlay.active = true
    }
},
enter (el, done) {
    Velocity(el, {
        marginLeft: 0
    }, {
        complete: done
    }, {
        duration: 300
    })
},
leave (el, done) {
    Velocity(el, {
        marginLeft: '-100%'
    }, {
        complete: done
    }, {
        duration: 300
    }).then(() => {
        if (this.isMobile) {
            this.globalOverlay.active = false
        }
    })
},

Conclusion

We now have our animation, and the toggle works with the menu I am going to share the full version of the code on github so you can grab it. The last step is to add the styles for the menu itself, and this is going to account for both the mobile and desktop. Once we add it our menu is done and we can start using it in our website. Make sure you watch the video for more guidance. 

#navbarNav {
    width: 100%;
}

.global-overlay {
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    background-color: rgba(1,1,1,.77);
    z-index: 50;
}

@media only screen and (min-width: 992px) {
    #navbarNav {
        float: right;
    }
}


@media only screen and (max-width: 991px) {
    .navbar {
        z-index: 100;
    }

    #navbarNav {
        display: block;
        margin-left: 0;
        position: absolute;
        top: 100%;
        left: 0;
        padding: 20px;
        background: rgb(248, 249, 250);
    }
}

See the code in github