uniapp自定义底部导航栏:基于 `uv-tabbar` 封装的底部导航栏组件
基于 `uv-tabbar` 封装的底部导航栏组件,支持**滚动智能显隐**——页面顶部常显、向下滚动隐藏释放全屏、停下或向上滚动立即显示。
目录结构
components/bottom-tabbar/
├── bottom-tabbar.vue # 底部导航栏组件(纯渲染,无硬编码配置)
├── tabbar.config.js # 【配置入口】tab 列表、颜色、图标等(增删 tab 改这里)
└── tabbar.js # 滚动追踪 composable(单例状态)
> `tabbar.js` 是独立于框架的纯状态管理,不依赖组件,可被 `plugins/index.js` 等外部模块导入。
bottom-tabbar.vue
<template>
<!-- 外层容器:用于实现滚动显隐的动画效果 -->
<view
class="bottom-tabbar"
:style="{
transform: tabbarVisible ? 'translateY(0)' : 'translateY(100%)',
transition: 'transform 0.32s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
}"
>
<!-- uv-ui 底部导航栏组件,所有配置由 tabbar.config.js 驱动 -->
<uv-tabbar
:value="currentTab"
:fixed="false"
:placeholder="false"
:safe-area-inset-bottom="true"
:border="true"
:active-color="config.activeColor"
:inactive-color="config.inactiveColor"
:icon-size="config.iconSize"
@change="onTabChange"
>
<!-- 遍历配置渲染每个 tab 项 -->
<uv-tabbar-item
v-for="item in config.tabs"
:key="item.name"
:name="item.name"
:text="item.showText !== false ? item.text : ''"
>
<!-- 选中图标插槽(自定义 image 替换 uv-ui 内置图标) -->
<template #active-icon>
<image
:src="item.activeIcon"
:style="{ width: config.iconSize + 'px', height: config.iconSize + 'px' }"
/>
</template>
<!-- 未选中图标插槽 -->
<template #inactive-icon>
<image
:src="item.inactiveIcon"
:style="{ width: config.iconSize + 'px', height: config.iconSize + 'px' }"
/>
</template>
</uv-tabbar-item>
</uv-tabbar>
</view>
</template>
<script setup>
import { ref, onMounted } from "vue"
import { useTabbar } from "./tabbar"
import { tabbarConfig } from "./tabbar.config"
// 直接使用配置对象(静态导入,不会被响应式代理包裹)
const config = tabbarConfig
// tab 名称 → 页面路径 的映射,用于 tabChange 时跳转
const tabPaths = Object.fromEntries(
config.tabs.map((t) => [t.name, t.path])
)
// 页面路径 → tab 名称 的映射,用于 onMounted 时匹配当前 tab
const pathToTab = Object.fromEntries(
config.tabs.map((t) => [t.path, t.name])
)
// 滚动显隐状态(由 tabbar.js 中的单例管理)
const { visible: tabbarVisible } = useTabbar()
// 当前选中的 tab 名称,默认取配置中第一个 tab
const currentTab = ref(config.tabs[0]?.name || "home")
// 组件挂载时根据当前页面路由匹配高亮的 tab
onMounted(() => {
const pages = getCurrentPages()
if (pages.length > 0) {
// 取页面栈最后一页的路由(如 "pages/index"),拼成完整路径
const route = `/${pages[pages.length - 1].route}`
currentTab.value = pathToTab[route] || config.tabs[0]?.name || "home"
}
})
// tab 切换回调:跳转到对应页面
function onTabChange(name) {
const path = tabPaths[name]
// 路径不存在或与当前 tab 相同时跳过
if (!path || currentTab.value === name) return
currentTab.value = name
// 使用 reLaunch 关闭所有页面栈,与原生 tabBar 的 switchTab 行为等价
uni.reLaunch({ url: path })
}
</script>
<style lang="scss" scoped>
.bottom-tabbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
will-change: transform;
}
</style>
tabbar.config.js
// 底部导航栏配置
// 集中管理所有 tab 项、颜色、图标,增删 tab 只改此处即可
export const tabbarConfig = {
// 选中状态的文字/图标颜色
activeColor: '#2f855a',
// 未选中状态的颜色
inactiveColor: '#909399',
// 图标尺寸(单位 px)
iconSize: 22,
// tab 列表,按顺序从左到右渲染
tabs: [
{
name: 'home', // tab 唯一标识,用于匹配选中状态
text: '首页', // 显示的文本
showText: true, // 是否显示文本,false 则只显示图标
inactiveIcon: '/static/images/tabbar/home.png', // 未选中图标(绝对路径)
activeIcon: '/static/images/tabbar/home_.png', // 选中图标
path: '/pages/index', // 点击后跳转的页面路径
},
{
name: 'todo',
text: '任务清单',
showText: true,
inactiveIcon: '/static/images/tabbar/todo.png',
activeIcon: '/static/images/tabbar/todo_.png',
path: '/pages/work/todoList/index',
},
{
name: 'record',
text: '农事记录',
showText: true,
inactiveIcon: '/static/images/tabbar/record.png',
activeIcon: '/static/images/tabbar/record_.png',
path: '/pages/work/farmRecord/index',
},
{
name: 'compliance',
text: '合规状态',
showText: true,
inactiveIcon: '/static/images/tabbar/compliance.png',
activeIcon: '/static/images/tabbar/compliance_.png',
path: '/pages/work/compliance/index',
},
{
name: 'mine',
text: '我的',
showText: true,
inactiveIcon: '/static/images/tabbar/mine.png',
activeIcon: '/static/images/tabbar/mine_.png',
path: '/pages/mine/index',
},
],
}
tabbar.js
import { ref } from 'vue'
/** 上次滚动位置,用于判定滚动方向 */
let lastScrollTop = 0
/** 是否可见(向下滚动隐藏,向上/停止/顶部时显示) */
const visible = ref(true)
/** 滚动停止后的空闲定时器(停顿后自动显示) */
let idleTimer = null
/**
* 更新滚动状态,由全局 onPageScroll 调用
* @param {number} scrollTop 当前滚动距离
*/
function updateScroll(scrollTop) {
const st = Number(scrollTop) || 0
if (st <= 0) {
visible.value = true
} else if (st > lastScrollTop) {
visible.value = false
clearTimeout(idleTimer)
} else if (st < lastScrollTop) {
visible.value = true
clearTimeout(idleTimer)
} else {
clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
visible.value = true
}, 150)
}
lastScrollTop = st
}
/** 重置为可见(页面切入前台时调用) */
function resetVisible() {
lastScrollTop = 0
visible.value = true
clearTimeout(idleTimer)
}
export function useTabbar() {
return {
visible,
updateScroll,
resetVisible,
}
}
快速接入(3 步)
第一步:复制组件
将 `components/bottom-tabbar/` 整个目录复制到目标项目的 `components/` 下。
确保目标项目已安装 `uv-tabbar` 及其依赖(`uv-icon`、`uv-badge`、`uv-safe-bottom`)。如果未安装,在 `uni_modules` 中添加:
- `uv-tabbar`
- `uv-icon`
- `uv-badge`
- `uv-safe-bottom`
uniapp直接插件市场安装:uv-ui 破釜沉舟DCloud 插件市场
第二步:注册滚动监听
在 `plugins/index.js`(或 `App.vue` 的 global mixin)中添加 tabbar 的滚动追踪:
import { useTabbar } from '@/components/bottom-tabbar/tabbar'
const tabbar = useTabbar()
// 在全局 mixin 中:
onPageScroll(e) {
const st = e?.scrollTop || e?.detail?.scrollTop || 0
tabbar.updateScroll(st)
}
onShow() {
tabbar.resetVisible()
}
如果项目已有类似 `plugins/index.js` 的入口文件,参考示例:
// plugins/index.js
import { useTabbar } from '@/components/bottom-tabbar/tabbar'
export function install(app) {
const tabbar = useTabbar()
app.mixin({
onShow() {
tabbar.resetVisible()
},
onPageScroll(e) {
const st = e?.scrollTop || e?.detail?.scrollTop || 0
tabbar.updateScroll(st)
}
})
}
第三步:在页面中添加组件
在需要显示底部导航栏的页面模板末尾添加:
<template>
<!-- 页面内容 -->
<bottom-tabbar />
</template>
> 建议写在 `</template>` 闭合之前,作为模板最后一个子节点。
自定义 tab
打开 `tabbar.config.js`,修改 `tabs` 数组即可,无需改动组件模板或脚本。
// tabbar.config.js
export const tabbarConfig = {
activeColor: '#2f855a', // 选中颜色
inactiveColor: '#909399', // 未选中颜色
iconSize: 22, // 图标大小
tabs: [
{
name: 'home', // tab 唯一标识
text: '首页', // 显示文本
showText: true, // 是否显示文本(false 仅显示图标)
inactiveIcon: '/static/images/tabbar/home.png', // 未选中图标
activeIcon: '/static/images/tabbar/home_.png', // 选中图标
path: '/pages/index', // 页面路径
},
// 追加或删除 tab 对象即可
],
}
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string |
tab 唯一标识 |
text |
string |
显示文本 |
showText |
boolean |
是否显示文本,默认 true。设为 false 时仅显示图标 |
inactiveIcon |
string |
未选中图标,支持 uv-ui 内置图标名或绝对路径图片 |
activeIcon |
string |
选中图标,支持 uv-ui 内置图标名或绝对路径图片 |
path |
string |
uni-app 页面路径 |
> 组件自动根据 `tabs` 数组生成路径映射,增删 tab **只需改这一个文件**,无需修改 `bottom-tabbar.vue`。
滚动显隐行为
| 场景 | tabbar 状态 |
|---|---|
| 页面顶部(scrollTop = 0) | 显示 |
| 向下滚动 | 立即隐藏,内容全屏展示 |
| 向上滚动 | 立即显示 |
| 滚动停止约 150ms | 自动显示 |
行为由 `tabbar.js` 中的 `updateScroll()` 控制,无需额外配置。
属性和方法
`useTabbar()` 返回值
| 名称 | 类型 | 说明 |
|---|---|---|
visible |
Ref<boolean> |
响应式显隐状态,组件内部读取 |
updateScroll(scrollTop) |
Function |
由全局 onPageScroll 调用,传入当前滚动距离 |
resetVisible() |
Function |
页面 onShow 时重置为可见 |
`<bottom-tabbar />` 组件
纯展示组件,所有配置由 `tabbar.config.js` 驱动,组件自身无 prop。修改 tab 列表、颜色等请编辑 `tabbar.config.js`。
迁移指南:从原生 tabBar 切换到自定义 bottom-tabbar
如果你的项目原本使用 uni-app 原生的 `pages.json` 中的 `tabBar` 配置,切换到本组件需要修改以下文件。
涉及文件一览
| # | 文件路径 | 操作 | 说明 |
|---|---|---|---|
| 1 | pages.json |
删除 tabBar 配置块 |
移除原生 tabBar 定义 |
| 2 | plugins/index.js |
修改 + 新增导入 | 注册 useTabbar 滚动追踪 |
| 3 | plugins/tab.js |
修改 | switchTab 内部改用 uni.reLaunch |
| 4 | permission.js |
删除 | 拦截器列表移除 switchTab |
| 5 | pages/index.vue |
新增 <bottom-tabbar /> |
添加自定义导航栏 |
| 6 | pages/xx/xxx/index.vue |
新增 <bottom-tabbar /> |
同上 |
| 7 | pages/xx/xx1/index.vue |
新增 <bottom-tabbar /> |
同上 |
| 8 | pages/xx/xx2/index.vue |
新增 <bottom-tabbar /> |
同上 |
| 9 | pages/mine/index.vue |
新增 <bottom-tabbar /> |
同上 |
| 10 | 页面内 uni.switchTab() 调用 |
改为 uni.reLaunch() |
原生 API 不再支持 |
详细操作步骤
1. `pages.json` — 删除原生 tabBar
删除 `pages.json` 中的整个 `tabBar` 配置段。以下是需要删除的内容示例:
{
"pages": [ /* ... 页面列表 ... */ ],
// 删除以下整个 tabBar 块 ↓↓↓
"tabBar": {
"color": "#000000",
"selectedColor": "#000000",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index",
"iconPath": "static/images/tabbar/home.png",
"selectedIconPath": "static/images/tabbar/home_.png",
"text": "首页"
},
// ... 其余 4 个 tab
]
},
// ↑↑↑ 删除到此处
"globalStyle": { /* ... */ }
}
2. `plugins/index.js` — 注册全局滚动追踪
diff
import { useBackTop } from '@/components/gap-back-top/backTop'
+ import { useTabbar } from '@/components/bottom-tabbar/tabbar'
export function install(app) {
const backTop = useBackTop()
+ const tabbar = useTabbar()
app.mixin({
onShow() {
backTop.resetScrollTop()
+ tabbar.resetVisible()
},
onPageScroll(e) {
const st = e?.scrollTop || e?.detail?.scrollTop || 0
backTop.setScrollTop(st)
+ tabbar.updateScroll(st)
}
})
}
3. `plugins/tab.js` — 修改 switchTab 实现
原生 tabBar 使用 `uni.switchTab()` 跳转,移除后统一使用 `uni.reLaunch()`:
diff
switchTab(url) {
- return uni.switchTab({ url })
+ return uni.reLaunch({ url })
}
4. `permission.js` — 移除 switchTab 拦截器
diff
- let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"]
+ let list = ["navigateTo", "redirectTo", "reLaunch"]
5. 页面中添加 `<bottom-tabbar />`
在需要设置的 tab 面的模板末尾(`<gap-back-top />` 后)添加:
<template>
<!-- ... 页面内容 ... -->
<gap-back-top />
<bottom-tabbar />
</template>
涉及路径:
- `pages/index.vue`
xxx/xx/index.vue
6. 替换页面中的 `uni.switchTab()` 调用
如果页面代码中有直接调用 `uni.switchTab()` 的地方,全部改为 `uni.reLaunch()`。例如:
diff
- uni.switchTab({ url: "/pages/work/todoList/index" })
+ uni.reLaunch({ url: "/pages/work/todoList/index" })
注意事项
- **不要在非 tabbar 页面中使用** `bottom-tabbar`,它会导致底部出现多余的导航栏。
- **保证 tab 页面的 `onPageScroll` 能正常触发**。如果页面使用 `scroll-view scroll-y`,需要在 `scroll-view` 的 `@scroll` 事件中手动调用 `tabbar.updateScroll()`。
- 切换 tab 使用 `uni.reLaunch()` 关闭所有页面栈,与原生 tabBar 的 `switchTab` 行为等价。
- 组件依赖 `uv-tabbar` 系列组件,确保 `uni_modules` 中已安装。
更多推荐
所有评论(0)