Vue 中如何实现仿微信的导航栏?

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 等技术进行界面交互。同时,本例也可以为开发者提供参考,使得开发者可以构建更加灵活、人性化的移动端应用。