背景
在前端开发中,通常实现的导航栏切换时没有添加交互效果,今天就从0到1实现一个美观且交互流畅的导航栏。
直接看效果:

1.导航栏实现思路
实现的导航栏需要具备丝滑过渡动画,切换选项时,指示器通过自定义贝塞尔曲线(cubic-bezier(0.68, -0.55, 0.265, 1.55))实现灵动过渡效果。
2. 案例代码
下面逐部分拆解代码,带你理解每个模块的作用。
2.1 模板结构
代码如下:
xml
<template>
<div class="nav-container">
<!-- 渐变指示器:核心动画元素 -->
<div class="indicator" :style="indicatorStyle"></div>
<!-- 导航容器:包裹所有导航项 -->
<div class="nav">
<!-- 循环渲染导航项 -->
<div
v-for="(tab, index) in tabs"
:key="index"
class="nav-item"
:class="{ active: activeTab === index }"
@click="changeTab(index)"
>
<span>{{ tab.label }}</span>
</div>
</div>
</div>
</template>
- 层级关系:nav-container 作为最外层容器,内部包含 indicator(指示器)和 nav(导航项容器),通过 z-index 控制层级(指示器 z=0,导航项 z=2)
- 激活状态:通过 :class="{ active: activeTab === index }" 控制选中项的文字颜色(白色)
2.2 逻辑处理
主要控制交互与动画效果。
代码如下:
xml
<script setup>
import { ref, computed } from 'vue'
// 1. 导航数据:可根据需求扩展(如添加路由地址、图标等)
const tabs = [
{ label: '首页', value: 0 },
{ label: '分类', value: 1 },
{ label: '购物车', value: 2 },
{ label: '我的', value: 3 }
]
// 2. 选中项状态:用 ref 定义响应式变量
const activeTab = ref(0)
// 3. 指示器样式计算:用 computed 实时更新位置和宽度
const indicatorStyle = computed(() => ({
// 左偏移量:选中项索引 * (100% / 导航项数量)
left: `${activeTab.value * (100 / tabs.length)}%`,
// 宽度:均分导航栏(100% / 导航项数量)
width: `${100 / tabs.length}%`
}))
</script>
2.3 实现视觉与动画
核心代码如下:
xml
<style scoped>
/* 渐变指示器:核心视觉元素 */
.indicator {
position: absolute;
top: 0;
height: 100%;
background: linear-gradient(45deg, #4facfe 0%, #00f2fe 100%); /* 蓝青渐变 */
border-radius: 35px; /* 与容器圆角一致:避免棱角 */
/* 自定义过渡曲线:让动画更灵动(非匀速) */
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
z-index: 0; /* 指示器在最下层 */
}
</style>
3. 导航栏优化
基础版本已实现核心功能,下面分享几个实用的优化方向,满足更多场景需求。
3.1 添加 hover 效果:增强交互反馈
在 .nav-item 中添加 hover 样式,让未选中项也有交互反馈:
css
.nav-item:hover:not(.active) {
color: #4facfe; /* hover 时文字变浅蓝 */
transform: scale(1.05); /* 轻微放大:增强感知 */
}
3.2 支持路由跳转:适配单页应用
- 先引入 Vue Router:
javascript
import { useRouter } from 'vue-router'
const router = useRouter()
- 在 tabs 数组中添加路由地址:
lua
const tabs = [
{ label: '首页', value: 1, path: '/' },
{ label: '分类', value: 2, path: '/category' },
{ label: '购物车', value: 3, path: '/cart' },
{ label: '我的', value: 4, path: '/mine' }
]
- 修改 changeTab 方法,实现路由跳转:
ini
const changeTab = (index) => {
activeTab.value = index
router.push({ path: tabs[index].path })
}
3.3 自定义指示器样式
代码中注释了 "下划线样式" 的指示器,若不需要渐变背景,可替换为:
css
.indicator {
position: absolute;
top: auto;
bottom: 0; /* 下划线靠底部 */
height: auto;
border-bottom: 2px solid #4facfe; /* 下划线颜色 */
border-radius: 0; /* 取消圆角 */
transition: all 0.3s ease; /* 简化过渡曲线 */
}
3.4 支持触摸滑动
若需要在移动端支持 "触摸滑动切换",可结合 touchstart 和 touchend 事件,计算滑动距离来判断是否切换导航项,核心逻辑如下:
typescript
// 记录触摸起始位置
const startX = ref(0)
// 触摸开始
const handleTouchStart = (e) => {
startX.value = e.touches[0].clientX
}
// 触摸结束
const handleTouchEnd = (e) => {
const endX = e.changedTouches[0].clientX
const diffX = endX - startX.value
// 滑动距离超过 50px 才切换(避免误触)
if (diffX > 50 && activeTab.value > 0) {
activeTab.value--
} else if (diffX < -50 && activeTab.value < tabs.length - 1) {
activeTab.value++
}
}
// 在模板中添加触摸事件
<div class="nav" @touchstart="handleTouchStart" @touchend="handleTouchEnd">
<!-- 导航项 -->
</div>
4. 完整代码
完整代码如下(可直接复制使用):
xml
<template>
<div class="nav-container">
<div class="indicator" :style="indicatorStyle"></div>
<div
class="nav"
@touchstart="handleTouchStart"
@touchend="handleTouchEnd"
>
<div
v-for="(tab, index) in tabs"
:key="tab.value"
class="nav-item"
:class="{ active: activeTab === index }"
@click="changeTab(index)"
>
<span>{{ tab.label }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// 导航数据(支持路由)
const tabs = [
{ label: '首页', value: 1, path: '/' },
{ label: '分类', value: 2, path: '/category' },
{ label: '购物车', value: 3, path: '/cart' },
{ label: '我的', value: 4, path: '/mine' }
]
const activeTab = ref(0)
const startX = ref(0)
// 指示器样式
const indicatorStyle = computed(() => ({
left: `${activeTab.value * (100 / tabs.length)}%`,
width: `${100 / tabs.length}%`
}))
// 切换导航(含路由跳转)
const changeTab = (index) => {
activeTab.value = index
router.push({ path: tabs[index].path })
}
// 触摸滑动逻辑(移动端适配)
const handleTouchStart = (e) => {
startX.value = e.touches[0].clientX
}
const handleTouchEnd = (e) => {
const endX = e.changedTouches[0].clientX
const diffX = endX - startX.value
if (diffX > 50 && activeTab.value > 0) {
activeTab.value--
router.push({ path: tabs[activeTab.value].path })
} else if (diffX < -50 && activeTab.value < tabs.length - 1) {
activeTab.value++
router.push({ path: tabs[activeTab.value].path })
}
}
</script>
<style scoped>
.nav-container {
position: relative;
width: 90%; /* 缩小宽度,避免贴边 */
margin: 20px auto; /* 居中显示 */
height: 44px;
background: #fff;
border-radius: 35px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.nav {
position: relative;
display: flex;
width: 100%;
height: 100%;
z-index: 1;
}
.nav-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
color: #555;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
z-index: 2;
}
.nav-item.active {
color: #fff;
}
.nav-item:hover:not(.active) {
color: #4facfe;
transform: scale(1.05);
}
.indicator {
position: absolute;
top: 0;
height: 100%;
background: linear-gradient(45deg, #ff9a9e 0%, #fecfef 100%);
border-radius: 35px;
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
z-index: 0;
}
</style>
5. 总结
最后总结一下:导航栏实现的核心思路就是是通过动态计算指示器样式,配合transition实现动画效果。
如有错误,请指正O^O!