做小程序时经常遇到这类需求:底部导航中间那个按钮要比其他四个更高、更大,做成一个圆形的凸起按钮,同时整个tabbar背景希望是半透明磨砂效果,能隐约透出页面滚动的内容。原生tabbar的配置项完全满足不了这种设计稿,这时候就必须走自定义tabbar的路线。本文把整个实现过程拆开讲清楚,包括目录结构、凸起按钮的定位方式、磨砂效果的实现细节以及兼容性处理。

一、开启自定义tabbar的基础配置
微信小程序从基础库2.5.0开始支持自定义tabbar。第一步要在app.json的tabBar节点里声明"custom": true,同时注意list数组依然要写,至少配置两个tab项,因为切换页面时小程序内核仍然依赖这个list来做页面识别。
{
"tabBar": {
"custom": true,
"color": "#999999",
"selectedColor": "#1989fa",
"backgroundColor": "#ffffff",
"list": [
{ "pagePath": "pages/index/index", "text": "首页" },
{ "pagePath": "pages/publish/publish", "text": "发布" },
{ "pagePath": "pages/mine/mine", "text": "我的" }
]
}
}接着在代码根目录(与pages同级)新建custom-tab-bar文件夹,里面放index.js、index.json、index.wxml、index.wxss四个文件。这个组件是小程序约定的全局组件,不需要在任何页面注册,tab页面加载时会自动挂载。index.json里声明"component": true即可。
有一个容易踩的坑:每个tab页都拥有独立的custom-tab-bar实例,切换页面时各实例之间不共享状态。所以在每个tab页的onShow里都要调用getTabBar方法更新选中态,否则会出现选中高亮不跟随页面的问题。
// 页面文件中,例如 pages/index/index.js
onShow() {
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 0 });
}
}二、中间按钮凸起效果的实现原理
凸起的本质是让中间按钮突破tabbar的高度限制往上溢出。整体布局思路是:tabbar容器使用flex布局横向排列五个位置,中间位置留一个占位元素(保持布局平衡),真正的圆形按钮用绝对定位悬浮在容器上方。父容器记得设置overflow: visible,否则按钮超出部分会被裁掉。
<view class="tab-bar">
<navigator url="/pages/index/index" class="tab-item">
<image src="{{selected === 0 ? iconHomeActive : iconHome}}"/>
<text>首页</text>
</navigator>
<view class="tab-item placeholder"></view>
<navigator url="/pages/mine/mine" class="tab-item">
<image src="{{selected === 2 ? iconMineActive : iconMine}}"/>
<text>我的</text>
</navigator>
<view class="fab-btn" bindtap="onPublish">
<image src="/assets/publish.png"/>
</view>
</view>样式部分的核心是fab-btn的定位值。假设tabbar高度100rpx,按钮直径112rpx,那么让按钮垂直居中于tabbar顶边即可,通过top取负值实现上浮。为了适配iPhone底部安全区,tabbar整体高度要用env(safe-area-inset-bottom)做底部延伸,这部分区域背景色要和tabbar保持一致,否则底部会露出一条黑边。
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
padding-bottom: env(safe-area-inset-bottom);
display: flex;
overflow: visible; /* 关键:允许按钮溢出 */
background: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(20rpx);
}
.fab-btn {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -56rpx; /* 一半高度凸出tabbar */
width: 112rpx;
height: 112rpx;
border-radius: 50%;
background: linear-gradient(135deg, #36c2ff, #1989fa);
box-shadow: 0 8rpx 20rpx rgba(24, 144, 255, 0.4);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}关于凸起按钮要不要做点击反馈,建议加一个hover-class配合scale缩放,按下时缩小到0.92倍,松开回弹,体验上会精致很多。另外按钮如果使用navigator跳转,目标页面必须在tabbar的list中;如果中间按钮是打开半屏发布面板这类非tab页面,就直接用view加bindtap,通过wx.navigateTo跳转。
三、半透明磨砂(毛玻璃)效果的细节与兼容
磨砂效果的实现依赖两个CSS属性配合:一是半透明背景色,例如rgba(255,255,255,0.75);二是backdrop-filter: blur(20rpx),它会对元素背后的内容做模糊处理,两者叠加才是真正的毛玻璃观感。只设半透明不设模糊,背景内容会清晰透出,显得杂乱;只设模糊不降低透明度,则看不到任何背后内容,等于白做。
兼容性方面需要注意,backdrop-filter在iOS上从微信内核支持较好,但部分安卓机型(尤其是低端机)的webview不支持该属性,表现为背景只是半透明而没有模糊。稳妥的降级方案是准备两层背景:先用一个稍深的半透明色兜底,支持backdrop-filter的设备上再叠加模糊。也可以通过判断系统做差异化样式:
// custom-tab-bar/index.js
Component({
data: {
selected: 0,
isIOS: true
},
lifetimes: {
attached() {
const info = wx.getSystemInfoSync();
this.setData({ isIOS: info.platform === 'ios' });
}
},
methods: {
onPublish() {
wx.navigateTo({ url: '/pages/publish/publish' });
}
}
});安卓不支持模糊时,把背景色的透明度调高一些(比如0.92),视觉上接近实底但不突兀,这是大多数成熟产品的折中做法。还有一个性能提示:backdrop-filter是实时计算的GPU合成效果,如果tabbar背后的页面存在高频重绘(例如长列表快速滚动),个别机型可能出现掉帧,磨砂半径不宜设得过大,一般控制在10px到20px之间比较安全。
最后再强调一个细节:磨砂效果要求页面内容能够滚动到tabbar底下才能体现出来。因此各tab页面的page样式不要设置与tabbar同色的底部padding封底,而是让内容自然延伸,由tabbar自身悬浮遮挡。调试时可以用开发者工具的真机调试模式在iOS和安卓真机上分别验证效果,模拟器对backdrop-filter的渲染和真机存在差异,不能只看模拟器。
整体方案跑通之后,自定义tabbar的可玩空间就打开了:可以给凸起按钮加呼吸动画引导用户发布,也可以在tabbar上挂全局消息红点,这些都是原生配置做不到的。建议把custom-tab-bar组件封装完整后沉淀为团队基础组件,后续项目直接复用。