默认情况下,小程序的顶部导航栏由系统自动生成,允许修改一些基本样式如背景色、文字颜色等。然而,如果需要实现更复杂的样式,如自定义图标、背景等,或者需要适配不同手机屏幕的高度和样式,就需要自定义顶部导航栏。
小程序自定义头部导航栏的开发主要涉及系统顶部状态栏和导航栏标题区域,自定义顶部导航栏的好处包括提高用户体验、增加页面交互性等。创建一个自定义导航栏组件,包含导航栏的样式和交互逻辑,并在需要使用导航栏的页面中引入自定义导航栏组件。
如下图,不同手机品牌的顶部区域范围存在差异,这就需要通过提供的相关API获取相应参数,以便根据当前设备屏幕的高度和样式,完成自定义顶部导航栏。
一、app.json参数配置
自定义导航栏首页需要在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",
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}
此时系统默认导航栏就不在了,如下图:
二、自定义导航栏组件
在小程序项目中新建components目录,用于存放自定义组件。在内部创建Header组件,并实现自定义导航栏的样式和交互功能。目录如下图:
首页在index.wxml编写自定义导航栏框架结构,在index.wxss中添加自定义导航栏的样式,index.js中实现导航栏中的交互功能。
index.wxml代码如下:
<!--components/Header/index.wxml-->
<view class="header-wrap">
<view class="header-fixed-wrap">
<view class="nav-bar"></view>
<view class="flex-box">
<view class="left">
<button type="default" class="btn-back">返回</button>
</view>
<view class="middle">
<view class="title">标题</view>
</view>
<view class="right"></view>
</view>
</view>
</view>
index.wxss代码如下:
/* components/Header/index.wxss */
.header-wrap { width: 100%; }
.header-wrap .nav-bar{ width: 100%; }
.header-fixed-wrap{
width: 100%;
background-color: #ffffff;
border-bottom: 1px solid #cccccc;
position: fixed;
left: 0;
top: 0;
z-index: 1000;
}
.header-fixed-wrap .flex-box{
width: 100%;
height: 80rpx;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.header-fixed-wrap .left, .header-fixed-wrap .right{ flex: 2; }
.header-fixed-wrap .middle{ flex: 5; }
.header-fixed-wrap .left .btn-back{
width: 80rpx; height: 60rpx; line-height: 60rpx; font-size: 28rpx; font-weight: normal; padding: 0; background: none;
}
.header-fixed-wrap .middle .title{
width: 80%; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0 auto;
}
这里导航栏是通过flex容器进行布局的,其相关属性参数说明如下:
- flex-direction:内部元素的排列方式(从左到右、从右到右、从上到下、从下到上)
- flex-wrap:子元素的换行方式(不换行,换行)
- justify-content:子元素的水平对齐方式
- align-items:子元素的垂直对齐方式
此时页面效果如下:
三、状态栏
如上图,导航栏悬浮在顶部,会发现与顶部状态栏重叠了,另外状态栏在不同设备上高度是不一致的,所以这里需要通过小程序提供的API获取状态的高度,动态修正导航栏位置。
index.js代码如下:
// components/Header/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
navBarHeight: 0
},
ready(){
const sysInfo = wx.getSystemInfoSync();
console.log(sysInfo)
},
/**
* 组件的方法列表
*/
methods: {
},
})
控制台输出结果可见,状态栏的高度statusBarHeight在接口函数中已返回,其单位为"px“。如下图:
此时,将index.wxml稍作修改,并在index.js完成状态栏高度获取并赋值即可。
index.wxml代码如下:
<!--components/Header/index.wxml-->
<view class="header-wrap">
<view class="header-fixed-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="flex-box">
<view class="left">
<button type="default" class="btn-back">返回</button>
</view>
<view class="middle">
<view class="title">标题</view>
</view>
<view class="right"></view>
</view>
</view>
</view>
index.js代码如下:
// components/Header/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
navBarHeight: 0
},
ready(){
const sysInfo = wx.getSystemInfoSync();
this.setData({
navBarHeight: sysInfo.statusBarHeight
});
},
/**
* 组件的方法列表
*/
methods: {
},
})
页面导航栏则显示正常了,如下图:
再换种机型,其状态栏较前面一个高出许多,但由于状态栏高度是动态获取,所以并不影响导航栏的正常显示。
四、局部引用
需要在首页中引入自定义导航栏组件,可以在首页目录中index.json中配置,代码如下:
{
"usingComponents": {
"Header": "./../../components/Header"
}
}
局部配置好后,则可以在首页中使用导航栏组件了,index.xwml代码如下:
<!--pages/index/index.wxml-->
<Header></Header>
<view>这是首页内容</view>
页面效果:
五、全局配置
当然,很多页面都需要自定义导航栏,则可以将它定义为全局,在app.json中usingComponents配置即可,代码如下:
{
"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"
}
}
页面index.wxml引入代码如下 :
<!--pages/index/index.wxml-->
<custom-header></custom-header>
<view>这是首页内容</view>
六、解决内容遮盖问题
写到这里,大家会发现首页的顶部内容,被导航栏遮挡无法显示,这是因为导航栏悬浮在页面上层,导致内容开始位置从状态栏的起始位置开始。
所以如下图,我们可以在导航栏下方添加一个不悬浮的块,用于填充导航栏的高度即可。
所以此时,我们将index.wxml再次修改,增加空白块用于填充导航栏高度,将页面内容顶到它该显示的位置。将Header组件中index.wxml和index.wxss进行调整,增加点占位块区域。
index.wxml代码如下:
<!--components/Header/index.wxml-->
<view class="header-wrap">
<!-- 占位块 -->
<view class="empty-header-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="empty-box"></view>
</view>
<view class="header-fixed-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="flex-box">
<view class="left">
<button type="default" class="btn-back">返回</button>
</view>
<view class="middle">
<view class="title">标题</view>
</view>
<view class="right"></view>
</view>
</view>
</view>
index.wxss代码如下:
/* components/Header/index.wxss */
.header-wrap { width: 100%; }
.header-wrap .nav-bar{ width: 100%; }
.header-fixed-wrap{
width: 100%;
background-color: #ffffff;
border-bottom: 1px solid #cccccc;
position: fixed;
left: 0;
top: 0;
z-index: 1000;
}
.header-fixed-wrap .flex-box{
width: 100%;
height: 80rpx;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.header-fixed-wrap .left, .header-fixed-wrap .right{ flex: 2; }
.header-fixed-wrap .middle{ flex: 5; }
.header-fixed-wrap .left .btn-back{
width: 80rpx; height: 60rpx; line-height: 60rpx; font-size: 28rpx; font-weight: normal; padding: 0; background: none;
}
.header-fixed-wrap .middle .title{
width: 80%; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0 auto;
}
/*填充导航栏区域高度占位块 样式*/
.empty-header-wrap{ width: 100%; position: relative; z-index: 1; }
.empty-header-wrap .empty-box{ width: 100%; height: 80rpx; }
此是页面中首页内容则可以正常显示了,效果如下图:
七、自定义标题
当然,每个页面显示的标题内容是不同的,所以此时需要在Header组件中,增加title属性,用于修改不同页面的标题内容。
indexjs中,定义title属性,代码如下:
// components/Header/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: "标题"
}
},
/**
* 组件的初始数据
*/
data: {
navBarHeight: 0
},
ready(){
const sysInfo = wx.getSystemInfoSync();
this.setData({
navBarHeight: sysInfo.statusBarHeight
});
},
/**
* 组件的方法列表
*/
methods: {
},
})
index.wxml中,将属性title填充到标题位置。代码如下:
<!--components/Header/index.wxml-->
<view class="header-wrap">
<!-- 占位块 -->
<view class="empty-header-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="empty-box"></view>
</view>
<view class="header-fixed-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="flex-box">
<view class="left">
<button type="default" class="btn-back">返回</button>
</view>
<view class="middle">
<view class="title">{{title}}</view>
</view>
<view class="right"></view>
</view>
</view>
</view>
此时在首页的index.wxml页面中,在组件上添加title属性,则可以修改标题内容了,代码如下:
<!--pages/index/index.wxml-->
<Header title="首页"></Header>
<view>这是首页内容</view>
首页导航栏的标题位置内容从”标题“变成了”首页“,效果如下图:
八、返回功能
讲到这里,导航栏上一直有一个”返回“按钮,下面对此进行讲解一下。由于这里是演示,所以未准备返回图标,直接用文字代替。
返回按钮是在子页面中才会出现,所以首页不应该显示此按钮。这就需要在组件加载里,动态判断该页面是否为子页面,并手动触发返回事件。
8.1 Header组件改造
index.js中,增加是否显示返回按钮的属性isShowBack,以及初始化当前页面是否需要显示返回按钮(initialBackStatus()初始化函数),并添加返回事件函数(backEvent()函数)。代码如下:
// components/Header/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: "标题"
}
},
/**
* 组件的初始数据
*/
data: {
navBarHeight: 0,
isShowBack: false, // 是否显示返回按钮
},
ready(){
this.initialStatusHeight();
this.initialBackStatus();
},
/**
* 组件的方法列表
*/
methods: {
// 初始化状态栏高度
initialStatusHeight(){
const sysInfo = wx.getSystemInfoSync();
this.setData({
navBarHeight: sysInfo.statusBarHeight
});
},
// 初始化返回按钮状态
initialBackStatus(){
const pages = getCurrentPages();
this.setData({
isShowBack: pages.length>=2
})
},
// 返回事件
backEvent(){
wx.navigateBack();
}
},
})
index.wxml中增加返回事件backEvent(),并判断是否显示返回按钮(通过isShowBack判断),代码如下:
<!--components/Header/index.wxml-->
<view class="header-wrap">
<!-- 占位块 -->
<view class="empty-header-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="empty-box"></view>
</view>
<view class="header-fixed-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="flex-box">
<view class="left">
<button type="default" class="btn-back" bind:tap="backEvent" wx:if="{{isShowBack}}">返回</button>
</view>
<view class="middle">
<view class="title">{{title}}</view>
</view>
<view class="right"></view>
</view>
</view>
</view>
此时,首页中返回按钮则不显示了,如下图
8.2 我的页面
首页在项目中再创建一个”我的“页面,用于跳转使用,并且使用全局导航栏组件。
index.wxml代码如下:
<!--pages/mine/index.wxml-->
<custom-header title="我的"></custom-header>
8.3 首页
在首页中添加按钮,用于跳转到我的页面,来测试下Header组件是否可以在子页面中显示返回按钮。
首页index.js代码如下:
// pages/index/index.js
Page({
/**
* 页面的初始数据
*/
data: {
},
// 跳转到站页
jump2MinePage(){
wx.navigateTo({
url: '/pages/mine/index',
});
},
})
首页index.wxml代码如下:
<!--pages/index/index.wxml-->
<Header title="首页"></Header>
<view>这是首页内容</view>
<button type="default" bind:tap="jump2MinePage">跳转到我的页面</button>
此时,首页则显示出跳转到”我的“页面的按钮,如下图:
当点击跳转后,查看”我的’页面效果,则“我的”页面作为子页面,则显示出返回按钮。如下图:
点击“返回”按钮后,则又跳转到回首页。
九、特殊导航栏(我的页面)
在小程序开发过程中,可能UI对某些页面设计高度个性化,顶部区域有较大区域为渐变色或图片等,比如一般情况“我的”页面设计会比较特殊。如下图:
有些人认为渐变图可以单独写在“我的”页面,这样也可以。不过这里放在Header组件中,是为了方便后续一些交互功能处理,具体如下介绍。
1、Header组件改造
index.js中添加backgroundImage和bgImageHeight属性,分别用来设置背景图和图片高度。代码如下:
// components/Header/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
// 导航栏标题
title: {
type: String,
value: "标题"
},
// 导航栏背景图
backgroundImage: {
type: String,
value: ""
},
// 图片高度
bgImageHeight: {
type: String,
value: ""
}
},
/**
* 组件的初始数据
*/
data: {
navBarHeight: 0,
isFixedBg: false, // 是否显示背景图
isShowBack: false, // 是否显示返回按钮
},
ready(){
this.setData({
isFixedBg: this.data.backgroundImage
})
this.initialStatusHeight();
this.initialBackStatus();
},
/**
* 组件的方法列表
*/
methods: {
// 初始化状态栏高度
initialStatusHeight(){
const sysInfo = wx.getSystemInfoSync();
this.setData({
navBarHeight: sysInfo.statusBarHeight
});
},
// 初始化返回按钮状态
initialBackStatus(){
const pages = getCurrentPages();
this.setData({
isShowBack: pages.length>=2
})
},
// 返回事件
backEvent(){
wx.navigateBack();
}
},
})
index.wxml中,在empty-header-wrap容器中,增加了背景图,并且判断背景图是否存在,代码如下:
<!--components/Header/index.wxml-->
<view class="header-wrap {{isFixedBg?'bg-image-wrap':''}}">
<!-- 占位块 -->
<view class="empty-header-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="empty-box"></view>
<image src="{{backgroundImage}}" style="height: {{bgImageHeight}};" mode="aspectFill" class="top-bg-img" wx:if="{{backgroundImage}}" />
</view>
<view class="header-fixed-wrap">
<view class="nav-bar" style="height: {{navBarHeight}}px"></view>
<view class="flex-box">
<view class="left">
<button type="default" class="btn-back" bind:tap="backEvent" wx:if="{{isShowBack}}">返回</button>
</view>
<view class="middle">
<view class="title">{{title}}</view>
</view>
<view class="right"></view>
</view>
</view>
</view>
index.wxss中增加了背景图相关样式,代码如下:
/* components/Header/index.wxss */
.header-wrap { width: 100%; }
.header-wrap .nav-bar{ width: 100%; }
.header-fixed-wrap{
width: 100%;
background-color: #ffffff;
border-bottom: 1px solid #cccccc;
position: fixed;
left: 0;
top: 0;
z-index: 1000;
}
.header-fixed-wrap .flex-box{
width: 100%;
height: 80rpx;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.header-fixed-wrap .left, .header-fixed-wrap .right{ flex: 2; }
.header-fixed-wrap .middle{ flex: 5; }
.header-fixed-wrap .left .btn-back{
width: 80rpx; height: 60rpx; line-height: 60rpx; font-size: 28rpx; font-weight: normal; padding: 0; background: none;
}
.header-fixed-wrap .middle .title{
width: 80%; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0 auto;
}
/*填充导航栏区域高度占位块 样式*/
.empty-header-wrap{ width: 100%; position: relative; z-index: -1; }
.empty-header-wrap .empty-box{ width: 100%; height: 80rpx; }
/*头部显示大图样式 - 调整*/
.top-bg-img{ width: 100%; position: absolute; top: 0; left: 0; z-index: -1; }
.header-wrap.bg-image-wrap .header-fixed-wrap{
background-color: transparent; border-bottom: 0;
}
.header-wrap.bg-image-wrap .header-fixed-wrap .middle .title{ color: #ffffff; }
2、我的页面
我的页面中,在导航栏组件上,添加刚才在Header组件中增加两个属性即可,代码如下:
<!--pages/mine/index.wxml-->
<custom-header title="我的" backgroundImage="/images/mine_bg.png" bgImageHeight="750rpx"></custom-header>
<view style="height: 500rpx;">我的页面内容1....</view>
<view style="height: 500rpx;">我的页面内容2....</view>
<view style="height: 500rpx;">我的页面内容3....</view>
<view style="height: 500rpx;">我的页面内容4....</view>
此时我的页面效果则已完成。背景图如下:
十、页面滚动导航样式调整
另外,当页面内容超出一屏时,页面处理滚动状态,则内容往上滑动时,与导航栏重叠时,则标题和内容重叠在一起,会影响页面的美观,此时则需要对状态栏作相应调整。如下图
为了解决这个问题,就需要监听页面滚动事件。需要注意的是onPageScroll滚动事件监听回调只有在页面中才生效,组件中无法监听,所以需要通过"我的”页面中监听数值传递给Header组件。
1、Header组件改造
index.js中增加scrollTop属性,用于接收页面中传递过来的滚动条高度,通过observer监听scrollTop值变化后,调整resetNavStyle()调整头部样式显示。代码如下:
// components/Header/index.js
let that = this;
Component({
/**
* 组件的属性列表
*/
properties: {
// 导航栏标题
title: {
type: String,
value: "标题"
},
// 导航栏背景图
backgroundImage: {
type: String,
value: ""
},
// 图片高度
bgImageHeight: {
type: String,
value: ""
},
// 滚动高度
scrollTop: {
type: Number,
value: 0,
observer: (v) => that.resetNavStyle(v)
}
},
/**
* 组件的初始数据
*/
data: {
navBarHeight: 0,
isFixedBg: false, // 是否显示背景图
isShowBack: false, // 是否显示返回按钮
},
ready(){
that = this;
this.setData({
isFixedBg: this.data.backgroundImage
})
this.initialStatusHeight();
this.initialBackStatus();
},
/**
* 组件的方法列表
*/
methods: {
// 调整导航样式
resetNavStyle(top){
this.setData({
isFixedBg: top<this.data.navBarHeight
})
},
// 初始化状态栏高度
initialStatusHeight(){
const sysInfo = wx.getSystemInfoSync();
this.setData({
navBarHeight: sysInfo.statusBarHeight
});
},
// 初始化返回按钮状态
initialBackStatus(){
const pages = getCurrentPages();
this.setData({
isShowBack: pages.length>=2
})
},
// 返回事件
backEvent(){
wx.navigateBack();
}
},
})
页面中图片显示样式是通过isFixedBg属性来控制的,当超出导航栏高度时,移除即可恢复之前导航栏样式,所以index.wxml和index.wxss中无须调整
2、我的页面
在我页面的index.js增加scrollTop属性记录当前滚动条高度,并添加监听事件onPageScroll回调获取当前滚动条高度。代码如下:
// pages/mine/index.js
Page({
/**
* 页面的初始数据
*/
data: {
scrollTop: 0
},
// 滚动事件监听
onPageScroll(e){
this.setData({
scrollTop: e.scrollTop
})
},
})
index.wxml中添加scrollTop属性,代码如下:
<!--pages/mine/index.wxml-->
<custom-header title="我的" backgroundImage="/images/mine_bg.png" bgImageHeight="750rpx" scrollTop="{{scrollTop}}"></custom-header>
<view style="height: 500rpx;">我的页面内容1....我的页面内容1我的页面内容1我的页面内容1我的页面内容1我的页面内容1我的页面内容1</view>
<view style="height: 500rpx;">我的页面内容2....</view>
<view style="height: 500rpx;">我的页面内容3....</view>
<view style="height: 500rpx;">我的页面内容4....</view>
此时,页面未超出导航栏高度,和超出后两种效果分别如下图所示:
当然,导航栏的多样性还有很多,这里先简单介绍以这里,希望对大家有所帮助。