Ready to use responsive Vue.js navbar

Hasan Parasteh
6 min readJun 20, 2020

--

I’m not a frontend developer who wants to create responsive things for users and maybe sometime we as backend developers should do some shitty stuff in frontend! so I created a fully functional reusable Vue component to remove the stress and headache of frontend / CSS stuff.

I consider you already created your Vue project but if you have not; below commands helps you to get started:

> npm install -g @vue/cli
> vue create “project-name”

When you run these commands successfully then you should run VS-Code to start coding the whole frontend stuff…

So I think I should explain the whole code step by step!

First of all, you should create a file under the component directory named “Navbar.vue”

Project Structure
Project Structure

Your project structure should be like this!

HTML Template

Then create an empty Vue template and type these below codes there:

<template>
<nav>
<div v-if="name" id="logo">
{{ name }}
</div>
<div v-else id="logo">
<img v-bind:src="logoImg" alt="Logo" />
</div>
<ul class="nav-links">
<li v-for="list in navLinks" :key="list.key">
<a v-if="list.dropdown === false" :href="list.link">{{ list.name }}</a>
<div class="dropdown-link" v-else>
<a :href="list.link">
{{ list.name }}

<span>&#x2193;</span>
</a>
<ul class="dropdown-menu">
<li v-for="item in list.dropdownLinks" :key="item.key">
<a :href="item.link">{{ item.name }}</a>
</li>
</ul>
</div>
</li>
</ul>
<div v-on:click="openMobileNav()" id="burger">
<div class="line1"></div>
<div class="line2"></div>
<div class="line3"></div>
</div>
</nav>
</template>

Okay, now we have the skeleton of our navbar! this part contains 3 part

Part One: Logo which has the id of “logo”
Part Two: Nav links which contain a for loop of navLinks list
Part Three: Burger icon for mobile users to make it responsive

1- if “logoImg” does not exist it will use “name” as the logo
2- navLinks list contains a list of objects which name(string), link(string), and dropdown (boolean) exists there
*: dropdown value used to check if a link has any child links or not
3- the burger is hidden on large screens and it’s only available on mobile

CSS & Styles

In this section, we will make `ul.nav-links` horizontal by using the flexbox feature of css3. after all nothing special goes on here…

The only tricky thing in this section is the dropdown menu which we should take care of! dropdown menu display should be absolute to make it place anywhere we want. Also, it should have a distance from top so we set 8vh on that option.

nav {display: flex;justify-content: space-around;align-items: center;background-color: #fe8888;min-height: 8vh;font-family: 'Montserrat', sans-serif;}div#logo {letter-spacing: 5px;color: #fefefe;font-weight: 800;font-size: 2rem;}ul.nav-links {display: flex;justify-content: space-between;width: 40%;line-height: 75px;}ul.nav-links li {list-style: none;}ul.nav-links a {text-decoration: none;color: #fefefe;font-size: 1.2rem;font-weight: 500;display: block;}#burger {display: none;cursor: pointer;}#burger div {width: 30px;height: 3px;margin: 8px;background-color: #fefefe;transition: all 0.3s ease-in;}ul.dropdown-menu {position: absolute;top: 8vh;background-color: #966d6d;min-width: 140px;cursor: pointer;display: none;}ul.dropdown-menu li:first-child {margin: 0 0 10px 0;}ul.dropdown-menu li {margin: 10px 0;}ul.dropdown-menu li:last-child {margin: 10px 0 0 0;}ul.dropdown-menu a {line-height: 8vh;padding: 5px 15px;background-color: #f1d1d1;line-height: 50px;}

The important section of CSS is the media queries which you can make stuff responsive!

/* Tablet */
@media screen and (max-width: 1024px) {
ul.nav-links {
width: 50%;
}
}
/* Mobile */
@media screen and (max-width: 768px) {
ul.nav-links {
position: absolute;
flex-direction: column;
width: 100%;
height: 92vh;
top: 8vh;
right: 0;
padding: 100px;
align-items: center;
justify-content: flex-start;
background-color: #fe8888;
opacity: 0.8;
transform: translateX(100%);
transition: transform 0.5s ease-in;
}
ul.nav-links li {
opacity: 0;
}
ul.nav-links a {
width: 100%;
}
div#burger {
display: block;
}
ul.dropdown-menu {
position: relative;
top: 0;
}
}

Now when you resize browser smaller(under 768px) all the elements placed perfectly. but still, we are missing something! Burger icon won’t work and dropdowns won’t show as well.

Javascript Functionality

Adding javascript into our code is a little tricky maybe you can do the same thing but with the whole different style of js coding. I use ES6 standard.

The main parts we are using in Vue is the method option to define our functions there and mounted for when the component mounted on the website and starts running(we use addEventListeners there).

export default {name: 'Navbar',props: ['name', 'logoImg', 'navLinks'],methods: {},mounted() {},}

if you observe the HTML part carefully you have noticed that there’s a `v-on:click` on the burger icon div. all it does is when you click on that icon it will trigger “openMobileNav” function.

openMobileNav() {
const burger = document.getElementById('burger')
const nav = document.querySelector('.nav-links')
const navLinks = document.querySelectorAll('.nav-links li')
// Toggle navigation on mobile
nav.classList.toggle('nav-active')
// Burger toggler
burger.classList.toggle('toggle')
// Animate navigation links
navLinks.forEach((link, index) => {
if (link.style.animation || link.style.webkitAnimation) {
link.style.animation = ''
link.style.webkitAnimation = ''
} else {
link.style.webkitAnimation = `navLinkFade 0.5s ease forwards ${
index / 7
}s`
link.style.animation = `navLinkFade 0.5s ease forwards ${index / 7}s`
}
})
}

As you see now we need to add some css classes in the html part to make it work. Bytheway it’s just toggle nav-active class and animate links when you click on burger icon.

.nav-active {
transform: translateX(0) !important;
}
.toggle .line1 {
transform: rotate(-45deg) translate(-9px, 10px);
}
.toggle .line2 {
opacity: 0;
}
.toggle .line3 {
transform: rotate(45deg) translate(-5px, -6px);
}
@keyframes navLinkFade {
from {
opacity: 0;
transform: translateX(-60px);
}
to {
opacity: 1;
transform: translateX(0px);
}
}

toggle class will turn the burger icon into X icon to represent closing to the user.

Now we can take care of dropdown menus…

openDropdownNav() {
const dropdownLink = document.querySelectorAll('.dropdown-link')
dropdownLink.forEach((dropdown) => {
dropdown.addEventListener('mouseover', () => {
dropdown.children[1].style.display = 'block'
})
dropdown.addEventListener('mouseleave', () => {
dropdown.children[1].style.display = 'none'
})
})
}

this function will open dropdown when the mouse enters the area of a tag in nav-links but it won’t work well on mobile! Why? because when you touch a hyperlink on mobile devices it means like a click. (mobiles dose not have hovers)

Solution: if user clicked a hyperlink in the mobile show the dropdown and if user clicked that link again open the link of itself.
okay, now we need a counter to check if the user clicked the link once or twice…

countClicksOnMobileDropdown() {
const dropdownLink = document.querySelectorAll('.dropdown-link')
dropdownLink.forEach((dropdown) => {
let counts = dropdown.clicks || 0
dropdown.addEventListener('click', () => {
counts++
if (counts % 2 == 0) {
window.location.href = dropdown.children[0].getAttribute('href')
} else {
event.preventDefault()
dropdown.children[1].style.display = 'block'
setTimeout(() => {
dropdown.children[1].style.display = 'none'
}, 5000)
}
})
setTimeout(() => {
counts = 0
}, 8000)
})
}

not working again :(

yeah, it won’t work until you attach these 2 dropdowns related functions in the mounting option of Vue.

mounted() {
this.openDropdownNav()
if (window.innerWidth < 768) {
this.countClicksOnMobileDropdown()
}
}

Done, it’s all working now :)))).

Why when I run `npm run serve` nothing will show on screen?

it’s because you simply need to attach this custom created component in App.vue part of our application.

App.vue:

<template>
<div id="app">
<Navbar
name="Logo Name"
:navLinks="[
{
name: 'Home',
link: '/home',
dropdown: false,
},
{ name: 'About', link: '/about', dropdown: false },{
name: 'Contacts',
link: '/contact',
dropdown: true,
dropdownLinks: [
{ name: 'A', link: '/pA', dropdown: false },
{ name: 'B', link: '/pB', dropdown: false },
],
},
{
name: 'Projects',
link: '/projects',
dropdown: true,
dropdownLinks: [
{ name: 'C', link: '/pA', dropdown: false },
{ name: 'D', link: '/pB', dropdown: false },
],
},
]"
/>
</div>
</template>
<script>
import Navbar from '@/components/Navbar.vue'
export default {
name: 'App',
components: {
Navbar,
},
}
</script>

I don’t personally created/have credits on this post. I walk through DevEd responsive navbar tutorial and add a dropdown feature to it!

Thank you DevEd for your great works we all appreciate you ❤

Also, I created a GitHub repo if you want the complete code and stuff to just copy/paste it on your project...

https://github.com/hasanparasteh/ResponsiveVueNavbar

--

--