微信小程序自定义底部导航栏

概要

微信小程序自定义底部导航栏,原生实现,不包含其他任何第三方组件,比较干净,开箱即用
效果预览:
微信小程序底部导航栏

功能

  1. 可自定义底部导航栏列表样式
  2. 可自定义每个菜单的默认、激活后的图标和文字样式
  3. 可自定义是否添加中间的大图标菜单,当然也可自定义大图标的默认与激活样式
  4. 可自定义激活动画,默认这个心跳过渡动画
  5. 可获取到底部导航栏高度,存在app全局变量中,其他页面有特殊需求需要动态计算页面高度时可能需要用到此属性
  6. 解决点击导航菜单时,激活的菜单貌似并不同步的问题
  7. 底部根据是否有安全距离自动调整

源码

不废话,直接贴上完整源码

源码结构
js

const app = getApp()
Component({
  /**
   * 组件的属性列表
   */
  properties: {

  },

  /**
   * 组件的初始数据
   */
  data: {
    selected: 0, // 激活的tab
    // tabber列表,与app.json一致
    color: "#666666",
    selectedColor: "#ff0000",
    backgroundColor: "#ffffff",
    // 注: list中的pagePath需要加'/'开头,否则在switchTab跳转url时会自带/src/pages开头导致路径错误
    list: [{
      "pagePath": "/src/pages/home/home",
      "icon": "./images/home.png",
      "iconActive": "./images/home-a.png",
      "text": "首页",
    }, {
      "pagePath": "/src/pages/monitor/monitor",
      "icon": "./images/relic.png",
      "iconActive": "./images/relic-a.png",
      "text": "文物监测"
    }, {
      "pagePath": "/src/pages/map/map",
      "icon": "./images/map.png",
      "iconActive": "./images/map.png",
      "center": true, // 中间大图标的参数,true时就会变大,默认非大图标
    }, {
      "pagePath": "/src/pages/news/news",
      "icon": "./images/hot.png",
      "iconActive": "./images/hot-a.png",
      "text": "文博资讯"
    }, {
      "pagePath": "/src/pages/mine/mine",
      "icon": "./images/mine.png",
      "iconActive": "./images/mine-a.png",
      "text": "我的"
    }],
    tabbarHeight: 0
  },

  lifetimes: {
    attached: function () {
      // 获取tab栏高度
      const query = wx.createSelectorQuery().in(this)
      query.select('#tabbar').boundingClientRect((res) => {
        // console.log('tab栏dom', res);
        this.setData({
          tabbarHeight: res.height
        })
        // 将tab栏高度设置进全局数据
        app.globalData.tabbarHeight = res.height
      }).exec()

      /**
       * 将初始化tabbar栏方法存入全局
       * @param {*} that 传入当前tab页面的this实例
       * @param {*} index 传入当前tab页面的索引
       * 若无该方法,会导致点击tab时激活的tab顺序错乱!
       * 例如在home首页onShow的生命周期钩子中,getApp().globalData.initTabbar(this, 0)
       */
      app.globalData.initTabbar = (that, index) => {
        if (typeof that.getTabBar === 'function' && that.getTabBar()) {
          that.getTabBar().setData({
            selected: index
          })
        }
      }
    }
  },

  /**
   * 组件的方法列表
   */
  methods: {
    switchTab(e) {
      // bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡
      const {
        index,
        url
      } = e.currentTarget.dataset
      if (this.data.selected !== index) {
        this.setData({
          selected: index
        })
        wx.switchTab({
          url: url,
        })
      }
    }
  }
})

wxml

<view class="tabbar" style="background-color: {{backgroundColor}};" id="tabbar">
  <view class="{{'tab'}} {{selected == index ? 'beat' : ''}}" wx:for="{{list}}" wx:key="item" catchtap="switchTab" data-index="{{index}}" data-url="{{item.pagePath}}">
    <view class="{{'tab-icon'}} {{item.center ? 'tab-icon-center' : ''}}" style="background-color: {{backgroundColor}};">
      <image style="width:100%;height:100%;" src="{{selected == index ? item.iconActive : item.icon}}" mode="" />
    </view>
    <view style="font-size:24rpx;color:{{selected == index ? selectedColor : color}}">
      {{item.text}}
    </view>
  </view>
</view>

wxss

.tabbar {
  position: fixed;
  bottom: 0;
  width: 100%;
  height: 120rpx;
  background: rgba(255, 255, 255, 1);
  box-shadow: 0 2px 16px rgba(184, 184, 210, 0.5);
  /* 利用ios新增的 env() 和 constant() 特性,自动计算底部安全距离 */
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);

  display: flex;
  justify-content: space-evenly;
}

.tab {
  width: 100%;
  height: 100%;
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  position: relative;
}

.tab-icon {
  width: 68rpx;
  height: 68rpx;
}

.tab-icon-center {
  width: 160rpx;
  height: 160rpx;
  position: absolute;
  top: -24rpx;
  padding: 12rpx;
  box-sizing: border-box;
  border-radius: 80rpx;
}

.beat {
  animation: beat 0.8s both;
}

@keyframes beat {
  0% {
    transform: scale3d(1, 1, 1);
  }
  30% {
    transform: scale3d(1.05, 0.95, 1);
  }
  40% {
    transform: scale3d(0.85, 1.15, 1);
  }
  50% {
    transform: scale3d(1.15, 0.85, 1);
  }
  65% {
    transform: scale3d(0.95, 1.05, 1);
  }
  75% {
    transform: scale3d(1.05, 0.95, 1);
  }
  100% {
    transform: scale3d(1, 1, 1);
  }
}

不要忘了在app.json文件中添加 tabBar 属性

"tabBar": {
    "custom": true,
    "color": "#666666",
    "selectedColor": "#ff0000",
    "backgroundColor": "#000000",
    "list": [
      {
        "pagePath": "src/pages/home/home",
        "text": "首页"
      },
      {
        "pagePath": "src/pages/monitor/monitor",
        "text": "监测"
      },
      {
        "pagePath": "src/pages/map/map",
        "text": "地图"
      },
      {
        "pagePath": "src/pages/news/news",
        "text": "资讯"
      },
      {
        "pagePath": "src/pages/mine/mine",
        "text": "我的"
      }
    ]
  }

细节

  • 注意上述js代码块中 initTabbar 方法,需要在每个tabbar页面使用
    例如首页是home,那么就在home.js中的onShow生命周期钩子中,写上getApp().globalData.initTabbar(this, 0)即可,参数中的0就是该tabbar的索引位置,第一个页面就是0,第二个就是1,以此类推
  • 注意页面路径问题,在代码块中有注释
  • 注意这个custom-tab-bar组件要放在根目录哦

改进

有问题和改进建议还请在评论区留言,觉得代码还不错的话,还望点个赞让更多人看到哦

  • 6
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏夜追凉丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值