微信小程序底部菜单栏是可以通过自定义来实现的。主要涉及:新建组件、编写组件代码、设置样式、配置导航栏、在页面中引用。自定义底部导航栏可以创建出符合项目设计需求效果,实现个性化的页面切换功能。
如下图效果,为比较常见的中间突出半圆效果的扫码或拍照的功能键,这个是可以通过CSS进行绘制出来的。此篇,将通过案例讲解这个导航栏如何实现。

此案例中实现功能,与之前一篇实现顶部导航栏的方式雷同,有兴趣朋友可以去了解下,地址:微信小程序 - 自定义头部导航栏开发-CSDN博客
一、配置导航栏
在app.json文件中配置navigationStyle为custom,代码如下:
{
  "pages": [
    "pages/index/index",
    "pages/logs/logs",
    "pages/mine/index"
  ],
  "window": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "Weixin",
    "navigationBarBackgroundColor": "#ffffff",
    "navigationStyle": "custom"
  },
  "style": "v2"
}二、新建组件
在项目components目录中,创建Navigation底部导航栏组件,如下图:

然后在app.json中,将其配置为全局组件,代码如下:
{
  "pages": [
    "pages/index/index",
    "pages/logs/logs",
    "pages/mine/index"
  ],
  "window": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "Weixin",
    "navigationBarBackgroundColor": "#ffffff",
    "navigationStyle": "custom"
  },
  "usingComponents": {
    "custom-header": "./components/Header/index",
    "custom-navigation": "./components/Navigation/index"
  },
  "style": "v2"
}然后页面中就可以直接使用custom-navigation引用了,代码如下:
<custom-navigation></custom-navigation>三、绘制背景效果
在页面中引入后,则可以进行编写菜单栏组件代码了,首先绘制出背景框。
index.wxml代码如下:
<!--components/Navigation/index.wxml-->
<view class="navigation-wrap">
  <view class="navigation-empty-wrap">
    <view class="empty-box"></view>
    <view class="bottom-safet-area" style="height: {{emptyHeight}}rpx;"></view>
  </view>
  <view class="fixed-nav-wrap">
    <view class="shadow-box">
      <view class="middle-circle"></view>
    </view>
    <view class="bottom-safet-area" style="height: {{emptyHeight}}rpx;"></view>
  </view>
</view>index.wxss代码如下:
/* components/Navigation/index.wxss */
.navigation-wrap{ width: 100%; }
.fixed-nav-wrap{ width: 100%; position: fixed; left: 0; bottom: 0; z-index: 100; }
.fixed-nav-wrap .shadow-box{ 
  width: 100%; 
  min-height: 100rpx; 
  background-color: #ffffff;
  box-shadow: 0 0 10rpx rgba(0, 0, 0, .2); 
  position: relative; 
  z-index: 1; 
}
.fixed-nav-wrap .shadow-box .middle-circle{ 
  width: 150rpx; 
  height: 150rpx; 
  margin-left: -75rpx;
  border-radius: 100rpx; 
  box-shadow: 0 0 10rpx rgba(0, 0, 0, .2); 
  background-color: #ffffff;
  position: absolute; 
  left: 50%; 
  top: -45rpx; 
  z-index: 1; 
}
.navigation-empty-wrap{ width: 100%; }
.navigation-empty-wrap .empty-box{ width: 100%; height: 100rpx; }
.bottom-safet-area{ background-color: #FFF; }
此时页面效果背景轮廓就出来了,虽然现在还不太美观,后期还需再度修饰一下。如下图:

这里注意的几个容器:
- navigation-empty-wrap类选择器空容器,为了解决悬浮导航不遮挡超出屏幕的内容,将页面中内容撑到底部菜单栏上面;
- fixed-nav-wrap类选择器为悬浮的菜单栏的最外层容器;
- shadow-box类选择器为底部轮廓样式(包含中间半圆样式);
- bottom-safet-area类选择器,为解决部分机型底部存在突出部分,如下图:

四、完成轮廓绘制
如上图可见,中间圆环,以及当底部有突出部分时出现的阴影,可以使用菜单容器进行覆盖,叠加在上层即可。
首先index.wxml中增加fixed-nav-content类容器,用于渲染菜单栏内容的容器,代码如下:
<!--components/Navigation/index.wxml-->
<view class="navigation-wrap">
  <view class="navigation-empty-wrap">
    <view class="empty-box"></view>
    <view class="bottom-safet-area" style="height: {{emptyHeight}}rpx;"></view>
  </view>
  <view class="fixed-nav-wrap">
    <view class="shadow-box">
      <view class="middle-circle"></view>
    </view>
   
    <view class="fixed-nav-content">
    </view>
    <view class="bottom-safet-area" style="height: {{emptyHeight}}rpx;"></view>
  </view>
</view>然后index.wxss中添加fixed-nav-content类选择器样式,代码如下:
/* components/Navigation/index.wxss */
.navigation-wrap{ width: 100%; }
.fixed-nav-wrap{ width: 100%; position: fixed; left: 0; bottom: 0; z-index: 100; }
.fixed-nav-wrap .shadow-box{ 
  width: 100%; 
  min-height: 100rpx; 
  background-color: #ffffff;
  box-shadow: 0 0 10rpx rgba(0, 0, 0, .2); 
  position: relative; 
  z-index: 1; 
}
.fixed-nav-wrap .shadow-box .middle-circle{ 
  width: 150rpx; 
  height: 150rpx; 
  margin-left: -75rpx;
  border-radius: 100rpx; 
  box-shadow: 0 0 10rpx rgba(0, 0, 0, .2); 
  background-color: #ffffff;
  position: absolute; 
  left: 50%; 
  top: -45rpx; 
  z-index: 1; 
}
.navigation-empty-wrap{ width: 100%; }
.navigation-empty-wrap .empty-box{ width: 100%; height: 100rpx; }
.bottom-safet-area{ background-color: #FFF; }
.fixed-nav-content{ 
    background-color: #ffffff; 
    width: 100%; 
    height: 120rpx; 
    position: absolute; 
    left: 0; 
    top: 0; 
    z-index: 5; 
}最后页面中则呈现出一个完美的底部菜单栏轮廓了,如下图:

五、菜单内容渲染
可以在index.js增加菜单数据,在容器中渲染菜单列表数据。在类容器fixed-nav-content中,创建模拟table类容器用于渲染菜单内容。
对于菜单列表需要对每项进行平分宽度,可以使用flex布局,也可以使用table + table-cell模拟表格属性实现。这里使用后者,table中的cell设置width:1%时,所有列会平分是因为会默认添加一个隐匿的table布局,其布局方式为table-layout: auto,且每个单元格有自己的最小宽度。
index.wxml代码如下:
<!--components/Navigation/index.wxml-->
<view class="navigation-wrap">
  <view class="navigation-empty-wrap">
    <view class="empty-box"></view>
    <view class="bottom-safet-area" style="height: {{emptyHeight}}rpx;"></view>
  </view>
  <view class="fixed-nav-wrap">
    <view class="shadow-box">
      <view class="middle-circle"></view>
    </view>
    <view class="fixed-nav-content">
      <view class="table">
        <view class="cell {{item.id===navId?'active':''}}" wx:for="{{navList}}" wx:key="index" data-id="{{item.id}}" bind:tap="jump2Event">
          <block wx:if="{{item.id==-1}}">
            <image src="../../images/scan.png" mode="aspectFill" class="scan" />
          </block>
          <block wx:else>{{item.name}}</block>
        </view>
      </view>
    </view>
    <view class="bottom-safet-area" style="height: {{emptyHeight}}rpx;"></view>
  </view>
</view>index.wxss代码如下:
/* components/Navigation/index.wxss */
.navigation-wrap{ width: 100%; }
.fixed-nav-wrap{ width: 100%; position: fixed; left: 0; bottom: 0; z-index: 100; }
.fixed-nav-wrap .shadow-box{ 
  width: 100%; 
  min-height: 100rpx; 
  background-color: #ffffff;
  box-shadow: 0 0 10rpx rgba(0, 0, 0, .2); 
  position: relative; 
  z-index: 1; 
}
.fixed-nav-wrap .shadow-box .middle-circle{ 
  width: 150rpx; 
  height: 150rpx; 
  margin-left: -75rpx;
  border-radius: 100rpx; 
  box-shadow: 0 0 10rpx rgba(0, 0, 0, .2); 
  background-color: #ffffff;
  position: absolute; 
  left: 50%; 
  top: -45rpx; 
  z-index: 1; 
}
.navigation-empty-wrap{ width: 100%; }
.navigation-empty-wrap .empty-box{ width: 100%; height: 100rpx; }
.bottom-safet-area{ background-color: #FFF; }
.fixed-nav-content{ 
  background-color: #ffffff; 
  width: 100%; 
  height: 120rpx; 
  position: absolute; 
  left: 0; 
  top: 0; 
  z-index: 5; 
}
.fixed-nav-content .table{ display: table; width: 100%; height: 100rpx; font-size: 26rpx; }
.fixed-nav-content .table .cell{ display: table-cell; vertical-align: middle; width: 1%; text-align: center;}
.fixed-nav-content .table .cell.active{ color: #FF872E; }
.fixed-nav-content .scan{ width: 60rpx; height: 60rpx; position: relative; top: -20rpx; }
index.js代码如下:
// components/Navigation/index.js
Component({
  /**
   * 组件的初始数据
   */
  data: {
    navList: [
      {id: 1, name: "首页", path: "/pages/index/index"},
      {id: 2, name: "列表", path: ""},
      {id: -1, name: "扫码", path: ""},
      {id: 3, name: "订单", path: ""},
      {id: 4, name: "我的", path: "/pages/mine/index"}
    ],
    emptyHeight: 0
  },
  ready(){
    this.initialHeight();
  },
  /**
   * 组件的方法列表
   */
  methods: {
    // 获取底部安全距离
    initialHeight(){
      const sysInfo = wx.getSystemInfoSync();
      this.setData({
        emptyHeight: sysInfo.windowHeight - sysInfo.safeArea.bottom
      })
    }
  }
})注意:index.js中initialHeight()函数,则是用来初始化部分机型底部突出区域高度计算的。
此时页面菜单栏效果如下图:

六、实现跳转
此案例中,创建了首页和我的 两个页面,分别在里面引入custom-navigation组件。

在custom-navigation组件index.js中添加跳转事件,代码如下:
// components/Navigation/index.js
Component({
  /**
   * 组件的初始数据
   */
  data: {
    navList: [
      {id: 1, name: "首页", path: "/pages/index/index"},
      {id: 2, name: "列表", path: ""},
      {id: -1, name: "扫码", path: ""},
      {id: 3, name: "订单", path: ""},
      {id: 4, name: "我的", path: "/pages/mine/index"}
    ],
    emptyHeight: 0
  },
  ready(){
    this.initialHeight();
  },
  /**
   * 组件的方法列表
   */
  methods: {
    // 获取底部安全距离
    initialHeight(){
      const sysInfo = wx.getSystemInfoSync();
      this.setData({
        emptyHeight: sysInfo.windowHeight - sysInfo.safeArea.bottom
      })
    },
    // 跳转
    jump2Event(e){
      const data = e.currentTarget.dataset,
            currentNav = this.data.navList.filter(item => item.id == data.id)[0];
      if(currentNav['path']) {
        wx.reLaunch({
          url: currentNav['path'],
        })
      } else if(data.id == -1){
        // 扫码
        wx.scanCode({
          success: res => {
            console.log('success', res);
          },
          fail: msg => {
            console.error(msg);
          }
        });
      }
      // if end
    }
  }
})当点击为中间扫码/拍照时,其data.id为-1,则启用摄像头;当点击其他菜单项时,如path链接存在,则实现跳转动作。
七、设置高亮
为实现进入某个页面,菜单栏上显示对应栏目的高亮效果,这则需要在每个引入页面定义菜单栏时,添加相应属性告诉组件需要将哪个菜单项添加高亮。
在组件中定义对外属性,navId为navList中每项对应的id值。代码如下:
// components/Navigation/index.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    navId: {
      type: Number,
      value: 1
    }
  },
  // 略...
})首页代码如下:
<custom-navigation navId="1"></custom-navigation>我的页面代码如下:
<custom-navigation navId="4"></custom-navigation>最后在custom-navigation组件的index.wxml中,通过id判断哪个为高亮部分。代码如下:
<view class="table">
	<view class="cell {{item.id===navId?'active':''}}" 
          wx:for="{{navList}}" 
          wx:key="index" 
          data-id="{{item.id}}" 
          bind:tap="jump2Event">
	  <block wx:if="{{item.id==-1}}">
		<image src="../../images/scan.png" mode="aspectFill" class="scan" />
	  </block>
	  <block wx:else>{{item.name}}</block>
	</view>
</view>当navId与item.id相等时,追加active类选择器,实现高亮效果。



















