小程序自定义顶部导航栏实例

小程序的顶部导航栏也就是navigationBar是指,下面图中白色的部分:

image.png

它由两部分组成,第一部分为手机的电量、时间等状态那一栏,第二部分就是下面的导航栏的主体部分。不自定义的话,这部分是处于页面之外的,除了默认行为无法操作,且滚动条不会涉及到那部分。但在有些页面,为了更美观,想将导航栏的背景色设置为透明,达到通顶的效果,比如这张设计图:
image.png

navigationBar可配置的属性并不能达到效果:
image.png

所以我们需要自定义导航栏,先从配置开始:
navigationStyle属性设置成custom,如果是全局配置,也就是每个页面都采用自定义导航栏,那就在app.json中的window对象中配置,如果是针对个别页面,那就在页面的json文件中配置:

{
  "usingComponents": {
  },
  "navigationStyle": "custom"
}

如果设置了这个属性,看看我们页面现在的效果,先看我现在wxml:

<scroll-view scroll-y class="scroll-view">
  <text>超人鸭</text>
</scroll-view>

scroll-view我设置了一个背景色和高度为100vh,现在页面:

image.png

可以看到原来的导航栏都不见了,我的页面撑满了整个屏幕,而且包括手机电量那一栏,所以现在就需要自定义一个导航栏,也就是一个组件,我命名为customNavigation
image.png

在开始编写这个导航栏组件前,先说微信小程序的两个api

  1. wx.getSystemInfo,可以获取系统信息,通过它就可以获取手机电量、时间等状态那一栏的高度,示例代码:
    wx.getSystemInfo({
      success: e => {
        console.log(e.statusBarHeight)
      }
    })
  1. wx.getMenuButtonBoundingClientRect,获取菜单按钮(右上角胶囊按钮)的布局位置信息。坐标信息以屏幕左上角为原点。示例代码:
let capsule = wx.getMenuButtonBoundingClientRect();
console.log(capsule) // 是个对象,包含宽高,以及四个坐标

因为我们要用到其中的部分属性去设置我们自定义导航栏的宽高以及各种样式。上面说到顶部导航栏其实包括两个部分,一个是手机的状态栏,另一部分才是主体内容,我们的标题、图标等肯定是在主体内容里面。所以在组件里面需要用到手机状态栏的高度来设置padding-top;而右上角的按钮的位置、大小也是无法操作的,所以我们的主要内容应该要避开这个按钮,就需要获取这个胶囊按钮的信息,也就是上面说的两个api。这种全局的信息可以存储到全局变量中,也就是小程序的app.js中:

//app.js
App({
  onLaunch: function () {
    wx.getSystemInfo({
      success: e => {
        this.globalData.statusBarHeight = e.statusBarHeight
        let windowWidth = e.windowWidth
        let capsule = wx.getMenuButtonBoundingClientRect();
        this.globalData.capsule = capsule
        this.globalData.navigationRightWidth = (windowWidth - capsule.right) * 2 + capsule.width
        this.globalData.capsuleToRight = windowWidth - capsule.right
      }
    })
    console.log(this.globalData)
  },
  globalData: {
    statusBarHeight: undefined, // 当前手机顶部状态栏高度,单位px
    capsule: {}, // 右上角胶囊的位置信息,宽高以及相对圆点的四个坐标,单位px
    navigationRightWidth: undefined, // 胶囊宽度加上两倍胶囊距离右边的距离
    capsuleToRight: undefined // 胶囊距离右边的距离
  }
})

里面的单位全部都是px,有了这些信息后,开始编写我们的自定义导航组件,首先在customNavigation.js文件中将app中的全局变量赋值到组件的data中:

const appInstance = getApp() // 获取app实例
Component({
  // Component的其他属性这里忽略了
  data: {
    statusBarHeight: appInstance.globalData.statusBarHeight,
    capsule: appInstance.globalData.capsule,
    navigationRightWidth: appInstance.globalData.navigationRightWidth,
    capsuleToRight: appInstance.globalData.capsuleToRight
  }
})

然后我们既然把这个导航栏写成一个组件,那通用性就应该高一点,比如导航栏的颜色、文字的颜色、文字的内容都应该由外部定义,导航栏的颜色和文字的颜色可以由属性值传入,文字的内容我们可以用插槽来实现,由外部去定义。为了以后拓展做准备,插槽我使用具名插槽,所以在组件的js文件中添加一个配置,支持多个插槽,所以更改之后的customNavigation.js为:

// compoments/customNavigation.js
const appInstance = getApp()
Component({
  options: {
    multipleSlots: true  // 支持多个插槽
  },

  /**
   * 组件的属性列表
   */
  properties: {
    bgColor: {
      type: String
    },
    color: {
      type: String
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    statusBarHeight: appInstance.globalData.statusBarHeight,
    capsule: appInstance.globalData.capsule,
    navigationRightWidth: appInstance.globalData.navigationRightWidth,
    capsuleToRight: appInstance.globalData.capsuleToRight
  }
})

接下来就是最重要的customNavigation.wxml,我先把代码贴出来:

<view class="navgation-wrapper" style="height:calc(44px + {{statusBarHeight}}px);padding-top:{{statusBarHeight}}px;background:{{bgColor}}">
  <view class="navgation-content" style="width:calc(100% - {{navigationRightWidth}}px)">
    <view class="title" style="left: calc(50% + {{navigationRightWidth / 2}}px);color:{{color}}">
      <slot name="title"></slot>
    </view>
  </view>
</view>

其中最外层的view就代表整个导航栏,包括手机状态栏,接着下一层的view标签,它代表导航栏的主题部分,宽度已经减去右边胶囊的宽度,在下面就是标题的插槽,里面主要用到calc这个css的属性,里面的单位不仅可以写px,也可以写rpxvw等其他单位。当然,这个wxml还要结合wxss使用,customNavigation.wxss:

.navgation-wrapper{
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  box-sizing: border-box;
  transition: all 1s;
}
.navgation-content{
  height: 100%;
  position: relative;
}
.title{
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size:32rpx;
  font-family:PingFangSC-Medium,PingFang SC;
  font-weight:bold;
  color:rgba(255,255,255,0.85);
  line-height:42rpx;
}

接下来就是在页面中使用了,比如我现在的页面叫index,那先需要在index.json中引入这个组件:

{
  "usingComponents": {
    "custom-navigation": "/components/customNavigation"
  },
  "navigationStyle": "custom"
}

index.wxml:

<scroll-view scroll-y class="scroll-view">
  <custom-navigation bgColor="{{'red'}}" color="{{'#fff'}}">
    <text slot="title">自定义导航</text>
  </custom-navigation>
</scroll-view>

效果:

image.png

到这里,最基础的功能就实现了,接下拓展一下,上面说到有些页面可能要将导航栏置为透明的,然后在页面滚动下来的时候设置为其他颜色,所以现在测试一下,先改一下index.wxml:

<scroll-view scroll-y class="scroll-view" bindscroll="pageScroll">
  <custom-navigation bgColor="{{customNavigationBgColor}}" color="{{customNavigationColor}}">
    <text slot="title">自定义导航</text>
  </custom-navigation>
  <!-- 测试滚动 -->
  <view style="height:120vh;"></view>
</scroll-view>

接下来在index.js中定义事件:

//index.js
//获取应用实例
const appInstance = getApp()
const statusBarHeight = appInstance.globalData.statusBarHeight
Page({
  data: {
    customNavigationBgColor: 'transparent',
    customNavigationColor:'#fff'
  },
  pageScroll(event) {
    const scrollTop = event.detail.scrollTop
    if(scrollTop > (statusBarHeight + 44)) {
      this.setData({
        customNavigationBgColor: '#fff',
        customNavigationColor:'#000'
      })
    } else {
      this.setData({
        customNavigationBgColor: 'transparent',
        customNavigationColor:'#fff'
      })
    }
  },
  onLoad: function () {
  },
})

初始效果:


image.png

滚动一定距离后:


image.png

到这里就ok了,实现整个自定义导航栏的就讲完了,当然,所实现的功能还是比较基础,很多情况我还没写进去,比如左上角的回退按钮,这个也可以用插槽来实现。但总体逻辑就是这样,主要在于获取各种设备的位置信息去计算样式。
如果你有更好的做法,欢迎指教哦。
作者微信:Promise_fulfilled
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352