Vue 中如何实现仿微信的导航栏?
在前端开发中,导航栏通常是网站或应用中最常用的组件之一。在本文中,我们将介绍如何在 Vue 中实现仿微信的导航栏,并通过实例演示其具体实现方法。
#1. 什么是仿微信的导航栏?
仿微信的导航栏是指在移动端应用中,通过滚动页面时,导航栏的形态会发生改变,形成微信的导航栏效果。具体的效果如下图所示:
![仿微信导航栏](https://img-blog.csdn.net/2018122519472811?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JnaW5fYmFpY2hlbnlp/ font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/ 50|watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JnaW5fYmFpY2hlbnlp/ font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/ 50)
#2. 如何使用 Vue 实现仿微信的导航栏?
在 Vue 中实现仿微信的导航栏需要用到 Vue 组件,实现方法如下:
## (1)创建 NavigationItem 组件
NavigationItem 组件是仿微信导航栏中每个选项的组件,包括选项卡、下划线等元素。其代码如下:
<template>
<div class="navigation-item">
<div class="tab" :class="{ active: isActive }"
@click="$emit('item-click', tabName)">
{{ tabName }}
</div>
<div class="line" :class="{ active: isActive }"></div>
</div>
</template>
<script>
export default {
name: 'NavigationItem',
props: {
tabName: {
type: String,
required: true
},
isActive: {
type: Boolean,
required: true
}
}
}
</script>
<style scoped>
.navigation-item {
display: inline-block;
position: relative;
height: 44px;
padding: 0 16px;
}
.tab {
position: relative;
line-height: 44px;
font-size: 16px;
color: #000;
}
.line {
position: absolute;
bottom: 0;
height: 4px;
width: 0;
left: 50%;
transform: translateX(-50%);
border-radius: 4px;
background-color: #FF751A;
transition: all .3s ease-in-out;
}
.line.active {
width: 100%;
}
</style>
(2)创建 NavigationBar 组件
NavigationBar 组件是仿微信导航栏的主组件,包含多个 NavigationItem 子组件。其代码如下:
<template>
<div class="navigation-bar">
<div class="wrapper" v-if="wrapperVisible">
<div class="container" ref="container">
<div class="inner-container" ref="innerContainer"
:class="{ hide: !isHovering }">
<NavigationItem :tabName="item" :isActive="currentIndex === index"
v-for="(item, index) in navList"
:key="index" @item-click="handleItemClick" />
</div>
</div>
<div class="arrow-down></div>
</div>
<div class="fixed-header" v-else>
<NavigationItem :tabName="item" :isActive="currentIndex === index"
v-for="(item, index) in navList" :key="index"
@item-click="handleItemClick" />
</div>
</div>
</template>
<script>
import NavigationItem from './NavigationItem';
export default {
name: 'NavigationBar',
components: {
NavigationItem
},
props: {
navList: {
type: Array,
required: true
}
},
data() {
return {
currentIndex: 0,
wrapperVisible: false,
isHovering: false
}
},
methods: {
handleItemClick(tabName) {
this.currentIndex = this.navList.indexOf(tabName);
this.$emit('item-click', tabName);
},
handleMouseEnter() {
if (!this.wrapperVisible) {
this.wrapperVisible = true;
this.$nextTick(() => {
const containerRect = this.$refs.container.getBoundingClientRect();
const innerContainerRect =
this.$refs.innerContainer.getBoundingClientRect();
const containerWidth = containerRect.right - containerRect.left;
this.$refs.innerContainer.style.width =
`${Math.min(innerContainerRect.width, containerWidth)}px`;
});
}
this.isHovering = true;
},
handleMouseLeave() {
this.isHovering = false;
},
handleScroll() {
const scrollTop = this.$refs.container.scrollTop; // 获取容器滚动高度
if (scrollTop > this.containerHeight) {
this.wrapperVisible = false;
return;
}
this.wrapperVisible = true;
const percent = scrollTop / this.containerHeight; // 计算滚动百分比
const lineList = this.$refs.innerContainer.querySelectorAll('.line');
for (let i = 0; i < lineList.length - 1; i++) {
const leftLine = lineList[i];
const rightLine = lineList[i + 1];
const leftTab = leftLine.previousSibling; // 寻找左侧的选项卡
const rightTab = rightLine.previousSibling; // 寻找右侧的选项卡
if (percent >= leftTab.offsetLeft / this.containerHeight &&
percent <= rightTab.offsetLeft / this.containerHeight) {
leftLine.style.width =
(percent - leftTab.offsetLeft / this.containerHeight) * 100 + '%';
rightLine.style.width =
(rightTab.offsetLeft / this.containerHeight - percent) * 100 + '%';
}
}
}
},
mounted() {
// 获取容器高度
const headerHeight = this.$refs.container.querySelector('.fixed-header')
.offsetHeight;
this.containerHeight = this.$refs.container.offsetHeight - headerHeight;
// 绑定滚动事件
this.$refs.container.addEventListener('scroll', this.handleScroll);
},
beforeDestroy() {
// 取消绑定滚动事件
this.$refs.container.removeEventListener('scroll', this.handleScroll);
}
}
</script>
<style scoped>
.navigation-bar {
position: relative;
height: 44px;
background-color: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, .05);
z-index: 999;
}
.wrapper {
position: absolute;
top: 44px;
left: 0;
right: 0;
background-color: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, .05);
transition: all .3s ease-in-out;
}
.container {
height: 200px;
overflow-y: scroll;
}
.fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
}
.arrow-down {
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
}
.inner-container.hide {
display: none;
}
.navitation-item {
margin-right: 12px;
}
</style>
## (3)使用 NavigationBar 组件
在 Vue 中使用 NavigationBar 组件需要传入一个 navList 属性,包含导航栏的所有选项卡名称。同时,需要处理导航栏的点击事件。具体实现如下:
<template>
<div class="app">
<NavigationBar :navList="navList" @item-click="handleItemClick" />
...
</div>
</template>
<script>
import NavigationBar from './components/NavigationBar';
export default {
name: 'App',
components: {
NavigationBar
},
data() {
return {
navList: ['员工排班', '库存管理', '采购管理', '库存审核机制']
}
},
methods: {
handleItemClick(tabName) {
// 处理导航栏的点击事件
}
}
}
</script>
<style>
...
</style>
到此,仿微信导航栏的 Vue 实现就完成了。
#3. 总结
本文介绍了仿微信的导航栏,并通过实例演示了在 Vue 中如何实现。通过阅读本文,我们可以了解到如何在 Vue 中使用组件、props、事件等特性,以及如何利用计算属性和 ref 等技术进行界面交互。同时,本例也可以为开发者提供参考,使得开发者可以构建更加灵活、人性化的移动端应用。