使用UniApp开发应用时,原生导航栏虽然方便,但往往难以满足产品对视觉设计和交互细节的追求。比如,你可能需要在导航栏中嵌入搜索框、动态切换背景色,或者为页面切换添加流畅的过渡动画——这些需求在原生导航栏上几乎无法实现。本文将手把手带你构建一套完全可控的自定义导航栏组件,并结合页面动画和插槽机制,在微信小程序、App和H5三端实现一致的优异体验。所有代码均可在项目中直接复用,且全程无需第三方动画库。
一、自定义导航栏的核心挑战
在UniApp中隐藏原生导航栏只需在 pages.json 中设置 navigationStyle 为 custom,但随之而来的问题不少:
- 需要手动处理状态栏高度(特别是刘海屏区域)。
- 必须兼容胶囊按钮(微信小程序右上角)的位置,避免内容重叠。
- 页面切换时需要自定义动画,因为原生的滑动效果已失效。
- 组件需要足够的灵活性,以适应不同页面的标题、右侧按钮等差异化需求。
接下来,我们将逐一攻克这些问题,并最终构建一个功能完备的 CustomNavBar 组件。
二、获取系统信息与构建安全区域
不同设备的状态栏高度各异,UniApp 提供了 uni.getSystemInfoSync() 来获取设备信息。我们需要拿到 statusBarHeight 和胶囊按钮的布局信息。对于微信小程序,胶囊按钮的位置可通过 uni.getMenuButtonBoundingClientRect() 获得;对于App和H5,则可以设定一个默认的右侧安全距离。
// utils/system.js
export function getNavBarInfo() {
const systemInfo = uni.getSystemInfoSync();
let menuButtonInfo = null;
let navBarHeight = 44; // 默认导航栏高度
// 微信小程序环境获取胶囊按钮信息
// #ifdef MP-WEIXIN
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// 导航栏高度 = 胶囊按钮高度 + 上下间距
navBarHeight = menuButtonInfo.height + (menuButtonInfo.top - systemInfo.statusBarHeight) * 2;
// #endif
return {
statusBarHeight: systemInfo.statusBarHeight,
navBarHeight: navBarHeight,
totalHeight: systemInfo.statusBarHeight + navBarHeight,
menuButtonInfo: menuButtonInfo // 可能为null
};
}
这个方法返回了构建导航栏所需的所有关键尺寸。注意我们使用了条件编译来区分小程序环境,确保代码在其他平台也能正常运行。
三、构建可复用的 CustomNavBar 组件
我们将创建一个支持插槽的导航栏组件,允许外部插入标题、左侧返回按钮和右侧操作区。组件内部自动处理安全区域和胶囊避让。
创建组件文件 components/custom-nav-bar/custom-nav-bar.vue:
<template>
<view class="custom-nav-bar" :style="{ paddingTop: navInfo.statusBarHeight + 'px', height: navInfo.totalHeight + 'px' }">
<view class="nav-content" :style="{ height: navInfo.navBarHeight + 'px' }">
<!-- 左侧区域:默认显示返回按钮,也可通过插槽自定义 -->
<view class="nav-left">
<slot name="left">
<view v-if="showBack" class="back-btn" @click="handleBack">
<text class="back-icon"><</text>
</view>
</slot>
</view>
<!-- 中间标题区域 -->
<view class="nav-center">
<slot name="title">
<text class="title-text">{{ title }}</text>
</slot>
</view>
<!-- 右侧操作区(小程序会自动避让胶囊) -->
<view class="nav-right" :style="rightStyle">
<slot name="right"></slot>
</view>
</view>
</view>
</template>
<script>
import { getNavBarInfo } from '@/utils/system.js';
export default {
name: 'CustomNavBar',
props: {
title: {
type: String,
default: ''
},
showBack: {
type: Boolean,
default: true
}
},
data() {
return {
navInfo: getNavBarInfo()
};
},
computed: {
rightStyle() {
// 小程序环境下,右侧需要留出胶囊按钮的宽度
if (this.navInfo.menuButtonInfo) {
const menuBtn = this.navInfo.menuButtonInfo;
const systemInfo = uni.getSystemInfoSync();
const marginRight = systemInfo.windowWidth - menuBtn.left;
return {
marginRight: marginRight + 'px',
width: menuBtn.width + 'px' // 与胶囊等宽,保持视觉平衡
};
}
return {};
}
},
methods: {
handleBack() {
uni.navigateBack({
delta: 1,
fail: () => {
uni.switchTab({ url: '/pages/index/index' });
}
});
}
}
};
</script>
此组件的关键点在于:
- 通过
paddingTop为状态栏留出空间,内容仅展示在安全区域以下。 - 左侧默认提供返回按钮,但可以通过
v-if隐藏或通过具名插槽left完全替换。 - 中间标题支持字符串传递和插槽两种方式,方便放入搜索框等复杂内容。
- 右侧在小程序环境下自动计算避让距离,与胶囊按钮对齐;在App/H5中则保持自然布局。
四、在页面中使用自定义导航栏
首先在 pages.json 中将需要使用自定义导航栏的页面设置为 "navigationStyle": "custom"。然后直接在页面顶部引入组件即可。
{
"path": "pages/product/detail",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
}
接着在页面文件中使用组件:
<template>
<custom-nav-bar title="商品详情">
<template #right>
<view class="cart-icon" @click="goCart">
<text>购物车</text>
</view>
</template>
</custom-nav-bar>
<!-- 页面主体内容 -->
<scroll-view class="page-body">
<!-- 商品内容 -->
</scroll-view>
</template>
<script>
import CustomNavBar from '@/components/custom-nav-bar/custom-nav-bar.vue';
export default {
components: { CustomNavBar },
methods: {
goCart() {
uni.navigateTo({ url: '/pages/cart/index' });
}
}
};
</script>
至此,一个高度可控的导航栏已经可以工作。但页面切换依然使用的是UniApp默认的推入动画(在App和小程序上)。接下来,我们将为其添加自定义过渡动画。
五、自定义页面过渡动画
UniApp 的 navigateTo 等在App端支持自定义动画样式,但小程序端不支持直接配置。为了实现跨平台一致且流畅的页面转场,我们可以利用Vue的 <transition> 组件配合页面栈管理来模拟动画。由于小程序的每个页面是独立实例,需要借助全局状态和预定义动画类。
这里我们采用一个更通用的方案:在App端和H5端利用UniApp提供的 animationType 参数,而在小程序端通过CSS动画模拟。以下是一个兼容三端的跳转封装:
// utils/navigator.js
export function navigateWithAnimation(url, animationType = 'slide-in-right') {
// #ifdef APP-PLUS || H5
uni.navigateTo({
url: url,
animationType: animationType, // slide-in-right, slide-in-left, etc.
animationDuration: 300
});
// #endif
// #ifdef MP-WEIXIN
// 小程序不支持原生动画类型,通过全局事件触发页面内动画
uni.$emit('page-animation', animationType);
uni.navigateTo({
url: url
});
// #endif
}
在目标页面的 onLoad 中监听动画事件,并应用对应的动画类:
export default {
data() {
return {
pageAnimationClass: ''
};
},
onLoad() {
uni.$on('page-animation', (type) => {
this.pageAnimationClass = type === 'slide-in-right' ? 'animate-slide-in' : 'animate-fade-in';
});
},
onUnload() {
uni.$off('page-animation');
}
};
随后在模板的根元素上绑定动态类:
<view :class="['page-container', pageAnimationClass]">
<!-- 页面内容 -->
</view>
/* 在App.vue或全局样式中定义动画 */
.animate-slide-in {
animation: slideInRight 0.3s ease-out;
}
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.25s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
这套方案虽然在不同平台使用了差异化的底层实现,但对开发者暴露的接口是一致的,且最终视觉体验高度统一。
六、打造导航栏与页面联动的沉浸式效果
很多应用希望导航栏在页面滚动时动态改变背景透明度,例如从透明逐渐变为纯色。我们可以通过监听页面滚动事件,将滚动位置传入导航栏组件。
在页面中:
<template>
<custom-nav-bar :title="title" :opacity="navOpacity"></custom-nav-bar>
<scroll-view @scroll="onScroll" scroll-y class="page-body">
<!-- 内容区域 -->
</scroll-view>
</template>
<script>
export default {
data() {
return {
navOpacity: 0
};
},
methods: {
onScroll(e) {
const scrollTop = e.detail.scrollTop;
// 滚动超过100px时完全显示背景
this.navOpacity = Math.min(scrollTop / 100, 1);
}
}
};
</script>
在 CustomNavBar 组件中接收opacity属性并应用:
<view class="custom-nav-bar" :style="navStyle">
...
</view>
props: {
opacity: {
type: Number,
default: 1
}
},
computed: {
navStyle() {
return {
paddingTop: this.navInfo.statusBarHeight + 'px',
height: this.navInfo.totalHeight + 'px',
background: `rgba(255,255,255,${this.opacity})`,
borderBottom: this.opacity > 0.8 ? '1px solid #eee' : 'none'
};
}
}
这样,导航栏就能够随着滚动平滑地由透明变为白色,实现类似微信朋友圈的沉浸式头部效果。
七、兼容性处理与常见问题
在实际项目中,还需注意以下细节:
- TabBar页面:如果自定义导航栏用在TabBar页面,应移除返回按钮并处理
switchTab跳转。 - 页面高度计算:自定义导航栏后,页面内容需要减去
totalHeight的顶部偏移,可以使用calc(100vh - XXpx)或动态绑定样式。 - 微信小程序基础库:建议在基础库2.20.1以上使用,低版本可能缺少
getMenuButtonBoundingClientRect。 - 性能:滚动监听可以配合节流函数使用,避免频繁触发状态更新。
八、总结
本文从零构建了一套UniApp自定义导航栏方案,涵盖了安全区域适配、胶囊按钮避让、插槽扩展、页面过渡动画以及沉浸式交互等关键要素。所有代码均可在微信小程序、App和H5三端良好运行,且组件化设计使得复用成本极低。当你下一次面对产品经理提出的“导航栏要加搜索框”、“页面切换要左滑效果”、“头部要渐变透明”等需求时,这套方案将帮助你从容应对,彻底突破原生导航栏的限制。
自定义导航栏不仅仅是一个UI组件,它代表了你对应用整体体验的掌控力。掌握它,你的UniApp项目将焕发出原生般的精致质感。

