小程序

小程序自定义导航栏

记录了小程序自定义导航栏的过程

Yixuan Lang
2021-08-26
4 min

# 小程序自定义导航栏

在写小程序项目的时候,觉得小程序官网给出的顶部导航满足不了个人的开发需求。查看官方文档时看到可以自定义小程序的导航栏。这里就记录一下自定义小程序导航栏的过程吧 👏👏👏

# 一、为什么要自定义 navigationBar


  • 除了胶囊按钮以外,原生导航栏只会出现返回按钮和当用户打开的小程序最底层页面是非首页时,默认展示的“返回首页”按钮 。
  • 原生导航栏的标题文字的颜色只有黑白。
  • 布局无法改变,不能做定制。

# 二、自定义导航 VS 原生导航


在自定义导航栏时,先看一下微信小程序原生导航栏的组成。导航栏分为状态栏和标题栏两个部分,可以在全局 json 文件中的 window 属性下配置,也可以在页面单独的 json 文件中进行配置来控制其样式。

image-20210912121332868

image-20210911173505619

当设置了自定义导航栏 navgationBar 时,整个导航栏区域完全可以由我们自己控制,这样赋予了导航栏更多交互以及 UI 设计上的可能。另外,早在 2016 年微信已经能开始适配沉浸式状态栏,也就是去掉原生导航的同时,整个屏幕都成为来了可编程的区域,上面第三个喜茶的小程序就用到了沉浸式导航。

# 三、如何自定义导航


# 1. 隐藏原生导航栏

想要自定义导航栏,首先要去掉原生的导航栏。在全局的 app.json 文件配置window 中 navigationStyle(导航栏样式)为 costom,这样微信就放开了导航栏的控制权,只留下了孤零零的胶囊按钮,该按钮是无法被隐藏掉的。

如果只想在某个页面中使用自定义导航,也可以在单独的页面的 json 文件中设置该属性

image-20210911114844200

"window": {
    "navigationStyle": "custom"
}

当取消掉了原生的导航栏后,接下来就是根据需求自定义一个导航栏组件,将其放置在页面原生导航栏的位置上。

# 2. 获取自定义导航栏相关信息

首先需要明确导航栏的高度,之后在该范围内根据页面的需求进行内容的定制。导航栏的高度有一个明确的计算公式:导航栏高度 = 状态栏到胶囊的间距(胶囊上边界距离 - 状态栏高度) × 2 + 胶囊高度 + 状态栏高度

所以现在需要获取:状态栏高度 胶囊高度 胶囊距离上边界距离

对于不同的机型以及不同的系统,状态栏以及胶囊按钮的位置都是不确定的,所以要进行一些计算,来兼容各种机型。

jiaonang

# (1)获取胶囊按钮的布局信息

小程序的官方文档给出可以使用wx.getMenuButtonBoundingClientRect()API 获取到菜单胶囊按钮的布局位置信息

其返回值包含以下信息

属性 类型 说明
width number 宽度,单位:px
height number 高度,单位:px
top number 上边界坐标,单位:px
right number 右边界坐标,单位:px
bottom number 下边界坐标,单位:px
left number 左边界坐标,单位:px

根据这些信息就可以得到胶囊按钮的高度以及距离上边界的距离了

const menuButtonInfo = wx.getMenuButtonClientRect(); // 胶囊按钮相关信息
const menuButtonHeight = menuButtonInfo.height; // 胶囊按钮高度
const menuButtonTop = menuButtonInfo.top; // 胶囊按钮距上边距距离

# (2) 获取状态栏的高度

使用 wx.getSystemInfoSync() API 可以获取系统的相关信息,可以通过其中的 statusBarHeight 属性获取到状态栏的高度

const statusBarHeight = wx.getSystemInfoSync().statusBarHeight; // 状态栏的高度

这样计算导航栏所需要的三个数据都拿到啦,就可以求得导航栏 navigationBar 的高度了 👏👏👏

  • 导航栏高度 = 状态栏到胶囊的间距(胶囊上边界距离 - 状态栏高度) × 2 + 胶囊高度 + 状态栏高度

  • navigationBarHeight = (menuButtonTop - statusBarHeight) × 2 + menuButtonHeight + statusBarHeight

# 3. 代码实现

对于上述有关导航栏相关信息的计算,一般放在 app.js 的 onLauch 生命周期钩子函数中,也就当小程序初始化完成时调用,全局只会触发一次,不会造成频繁的计算相同的数据

// app.js文件
// app.js
App({
  onLaunch() {
	...
    // 调用函数设置自定义导航栏信息
    this.setNavBarInfo()
  },
  // 全局数据管理
  globalData: {
    userInfo: null,
    // 自定义导航栏相关信息
    navBarHeight: 0,  // 导航栏高度
    menuBottom: 0, // 胶囊距离底部间距
    menuRight: 0,  // 胶囊距离右边间距
    menuHeight: 0  // 胶囊高度
  },
  // 设置导航栏信息
  setNavBarInfo(){
    // 获取系统信息
    const systemInfo = wx.getSystemInfoSync()
    // 获取胶囊按钮位置信息
    const menuButtonInfo = wx.getMenuButtonBoundingClientRect()
    // 获取导航栏高度
    // 导航栏高度 = 状态栏到胶囊的间距(胶囊距上距离 - 状态栏高度)) * 2 + 状态栏高度 + 胶囊高度
    this.globalData.navBarHeight = (menuButtonInfo.top - systemInfo.statusBarHeight) * 2
    + menuButtonInfo.height + systemInfo.statusBarHeight
    this.globalData.menuBottom = menuButtonInfo.top - systemInfo.statusBarHeight
    this.globalData.menuRight = systemInfo.screenWidth - menuButtonInfo.right
    this.globalData.menuHeight = menuButtonInfo.height
  }
})

这样自定义导航栏的相关数据信息就可以在任意的页面中被访问到啦!!

要在某些页面中使用自定义导航栏的相关数据可以通过调用 getApp()方法来获取,获取到后赋值给该页面的初始数据,就可以在页面中使用这些数据来定位导航栏了

// index.js
const app = getApp();

Page({
  /**
   * 页面的初始数据
   */
  data: {
    navBarHeight: app.globalData.navBarHeight,
    menuBottom: app.globalData.menuBottom,
    menuRight: app.globalData.menuRight,
    menuHeight: app.globalData.menuHeight,
  },
});

但是这种方法仅适合只在个别页面需要自定义导航栏,或者自定义导航栏的布局区别较大的情况,一般可以封装成组件方便后期复用。

# 4. 封装成组件

一般在不同的页面上可能会显示不同的导航栏信息,为满足这种需求可以封装一个灵活的组件的来提高我们的开发效率。

# (1)组件结构 wxml

// components/Navigation/Navigation.wxml
<!-- 自定义导航栏组件 -->
<view class="nav-container" style="height: {{navBarHeight}}px">
  <view class="nav-box">
    <view
      class="navbar"
      style="height:{{menuHeight}}px; min-height:{{menuHeight}}px; line-height:{{menuHeight}}px; bottom:{{menuBottom}}px;"
    >
      <!-- 自定义内容区域 -->
      <slot></slot>
    </view>
  </view>
</view>

# (2) 组件 js

// components/Navigation/Navigation.js
const app = getApp();
Component({
  /**
   * 组件的属性列表
   */
  properties: {},
  /**
   * 组件的初始数据
   */
  data: {
    navBarHeight: app.globalData.navBarHeight,
    menuBottom: app.globalData.menuBottom,
    menuRight: app.globalData.menuRight,
    menuHeight: app.globalData.menuHeight,
  },
  methods: {},
});

# (3) 使用组件

比如想要在 index 页面引入自定义导航栏组件,首先在 index.json 文件中使用该组件

{
  "usingComponents": {
    "Navigation": "/components/Navigation/Navigation"
  }
}

之后就可以在页面中使用拉,在构建组件的时候使用 slot 插槽来放置自定义的导航栏内容,所以可以根据需求在组件的内部放入相应的内容

<Navigation>
  <text>Idex page</text>
</Navigation>

# 四、 可能会出现的问题

在学习自定义导航栏的时候,在网上看到文章说到在使用getMenuButtonBoundingClientRect方法时,在有些机型下不能成功获取到,这里先挖一个坑 😎,贴这篇文章,后续遇到该问题方便查询

文章地址