纯CSS&JS实现:丝滑渐变过渡的动态导航栏

背景

在前端开发中,通常实现的导航栏切换时没有添加交互效果,今天就从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 支持路由跳转:适配单页应用

  1. 先引入 Vue Router:
javascript 复制代码
import { useRouter } from 'vue-router'
const router = useRouter()
  1. 在 tabs 数组中添加路由地址:
lua 复制代码
const tabs = [
  { label: '首页', value: 1, path: '/' },
  { label: '分类', value: 2, path: '/category' },
  { label: '购物车', value: 3, path: '/cart' },
  { label: '我的', value: 4, path: '/mine' }
]
  1. 修改 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!

相关推荐
qq. 28040339841 小时前
vue介绍
前端·javascript·vue.js
未来之窗软件服务2 小时前
未来之窗昭和仙君(五十五)标签票据打印模板设计器——东方仙盟筑基期
前端·打印设计器·仙盟创梦ide·东方仙盟·昭和仙君·东方仙盟架构
Mr.Jessy2 小时前
Web APIs 学习第五天:日期对象与DOM节点
开发语言·前端·javascript·学习·html
前端大卫2 小时前
如何统一前端项目的 Node 版本和包管理器?
前端
Hi~晴天大圣2 小时前
HTML onclick用法
前端·html
!win !3 小时前
前端跨标签页通信方案(上)
前端·javascript
xw53 小时前
前端跨标签页通信方案(上)
前端·javascript
烛阴3 小时前
Python数据可视化:从零开始教你绘制精美雷达图
前端·python
全栈前端老曹3 小时前
【前端组件封装教程】第3节:Vue 3 Composition API 封装基础
前端·javascript·vue.js·vue3·组合式api·组件封装