Component:
<template>
<nav class="nav">
<a
v-for="link in links"
:key="link.id"
class="nav-item"
:class="{'is-active': link.id === value}"
:style="link.id === value ? {color: link.color} : null"
href
@click.prevent="$emit('input', link.id)"
v-text="link.label" />
<span class="nav-indicator" :style="indicatorStyle" />
</nav>
</template>
<script>
export default {
name: 'FancyNavbar',
props: {
value: {type: String, default: null},
links: {type: Array, required: true},
},
data() {
return {
indicatorStyle: {
width: 0,
left: 0,
backgroundColor: 'transparent',
},
};
},
watch: {
value: {
immediate: true,
handler(value) {
const link = this.links.find(({id}) => id === value);
if (link) {
this.$nextTick(() => this.updateIndicator(link));
}
},
},
},
mounted() {
if (!this.value && this.links.length > 0) {
this.$emit('input', this.links[0].id);
}
},
methods: {
updateIndicator(link) {
const activeElement = this.$el.querySelector('.is-active');
if (!activeElement) {
return;
}
this.indicatorStyle = {
width: `${activeElement.offsetWidth}px`,
left: `${activeElement.offsetLeft}px`,
backgroundColor: link.color,
};
},
},
};
</script>
<style scoped>
.nav {
display: inline-flex;
position: relative;
overflow: hidden;
max-width: 100%;
background-color: #fff;
padding: 0 20px;
border-radius: 40px;
box-shadow: 0 10px 40px rgba(159, 162, 177, .8);
}
.nav-item {
color: #83818c;
padding: 20px;
text-decoration: none;
transition: .3s;
margin: 0 6px;
z-index: 1;
font-family: 'DM Sans', sans-serif;
font-weight: 500;
position: relative;
}
.nav-item:before {
content: "";
position: absolute;
bottom: -6px;
left: 0;
width: 100%;
height: 5px;
background-color: #dfe2ea;
border-radius: 8px 8px 0 0;
opacity: 0;
transition: .3s;
}
.nav-item:not(.is-active):hover:before {
opacity: 1;
bottom: 0;
}
.nav-item:not(.is-active):hover {
color: #333;
}
.nav-indicator {
position: absolute;
left: 0;
bottom: 0;
transition: .4s;
height: 5px;
z-index: 1;
border-radius: 8px 8px 0 0;
}
@media (max-width: 580px) {
.nav {
overflow: auto;
}
}
</style>
Usage in another component:
<template>
<FancyNavbar v-model="activeLink" :links="links" />
</template>
<script>
import FancyNavbar from './FancyNavbar.vue';
export default {
name: 'ParentComponent',
components: {FancyNavbar},
data() {
return {
activeLink: 'home',
links: [
{id: 'home', label: 'Home', color: 'orange'},
{id: 'about', label: 'About', color: 'green'},
{id: 'testimonials', label: 'Testimonials', color: 'blue'},
{id: 'blog', label: 'Blog', color: 'red'},
{id: 'contact', label: 'Contact', color: 'rebeccapurple'},
],
};
},
watch: {
activeLink(newLink, oldLink) {
if (newLink !== oldLink) {
const link = this.links.find(({id}) => id === newLink);
console.log('navigate to: ' + link.label);
}
},
},
};
</script>
Thanks for the codepen link. I was about to do a very similar one to a project.
EDIT: forgot to set the active link text color when I first posted. Fixed now.