做微信小程序时,原生导航栏往往满足不了设计需求:首页要沉浸式背景图、详情页要透明渐变、品牌页要放 logo 和搜索框。这时就得用自定义头部(custom navigationBar)。我做过十几个带自定义头部的项目,把踩过的坑和可复用的写法整理成这篇,直接抄代码就能用。
一、先关掉原生导航栏
在页面 json 里配置:
{
"navigationStyle": "custom",
"disableScroll": false
}注意 navigationStyle: custom 只在页面级生效,app.json 里配也可以全局生效。关闭后页面会从屏幕最顶部开始渲染,状态栏区域会被内容覆盖,所以必须自己算高度。
二、核心:算出状态栏和胶囊高度
自定义头部高度 = 状态栏高度 + 导航栏高度。导航栏高度要和右上角胶囊按钮对齐,微信提供了 getMenuButtonBoundingClientRect 接口:
// 建议封装在 utils/navbar.js
export function getNavBarInfo() {
const sys = wx.getWindowInfo ? wx.getWindowInfo() : wx.getSystemInfoSync()
const statusBarHeight = sys.statusBarHeight || 20
let navBarHeight = 44
let statusBarGap = 4
try {
const menu = wx.getMenuButtonBoundingClientRect()
// 胶囊上下间距一致,导航栏高度 = 胶囊高度 + 上下间距
navBarHeight = (menu.top - statusBarHeight) * 2 + menu.height
statusBarGap = menu.top - statusBarHeight
} catch (e) {
// 极少数基础库不支持,走兜底值
}
return {
statusBarHeight,
navBarHeight,
totalHeight: statusBarHeight + navBarHeight,
menuLeft: sys.windowWidth - 10 - 87 // 胶囊左侧大致位置,用于避让
}
}
几个关键点:
- 优先用
wx.getWindowInfo(),getSystemInfoSync已被标记为不推荐,新项目别再用了。 - 导航栏高度不要写死 44,iPhone 和部分安卓机型胶囊位置不同,用胶囊反推最稳。
- 计算要在
onLoad里做一次缓存到 data,别放在渲染函数里反复调用。
三、组件化写法
把头部抽成自定义组件,页面里传标题和返回逻辑:
// components/nav-bar/index.js
Component({
properties: {
title: { type: String, value: '' },
bgColor: { type: String, value: '#ffffff' },
showBack: { type: Boolean, value: true }
},
data: { statusBarHeight: 20, navBarHeight: 44 },
lifetimes: {
attached() {
const sys = wx.getWindowInfo()
const menu = wx.getMenuButtonBoundingClientRect()
const statusBarHeight = sys.statusBarHeight
this.setData({
statusBarHeight,
navBarHeight: (menu.top - statusBarHeight) * 2 + menu.height
})
}
},
methods: {
onBack() {
const pages = getCurrentPages()
if (pages.length > 1) {
wx.navigateBack()
} else {
wx.reLaunch({ url: '/pages/index/index' })
}
}
}
})
<!-- components/nav-bar/index.wxml -->
<view class="nav" style="height:{{statusBarHeight + navBarHeight}}px;background:{{bgColor}}">
<view style="height:{{statusBarHeight}}px"></view>
<view class="nav-bar" style="height:{{navBarHeight}}px">
<view wx:if="{{showBack}}" class="back" bindtap="onBack">‹</view>
<view class="title">{{title}}</view>
</view>
</view>
<!-- 占位,避免页面内容被头部盖住 -->
<view style="height:{{statusBarHeight + navBarHeight}}px"></view>
注意标题要居中但又要避开胶囊,通常给标题容器加 padding: 0 100rpx 左右,或者直接用绝对定位 + left: 0; right: 0; text-align: center,再靠 pointer-events: none 防止挡住右侧操作区。
四、沉浸式与滚动渐变
首页想要背景图铺满状态栏,做法是头部 position: fixed,背景透明,页面顶部放一张图。滚动时监听 onPageScroll 动态改透明度:
onPageScroll(e) {
const opacity = Math.min(e.scrollTop / 100, 1)
if (Math.abs(opacity - this.data.navOpacity) > 0.02) {
this.setData({ navOpacity: opacity })
}
}
这里一定要做阈值判断,否则每帧都 setData 会明显卡顿。另外页面滚动时头部文字颜色也要跟着从白变黑,用两套 class 切换即可。
五、几个容易踩的坑
- 下拉刷新:自定义头部下,下拉刷新的 loading 会从顶部出现,和头部重叠。可以在页面 json 里开
"enablePullDownRefresh": true后配合backgroundTextStyle处理,或者干脆用scroll-view自己做下拉。 - 返回按钮热区:返回箭头别只给 20rpx 宽,热区至少 88rpx × 88rpx,否则用户点不中。
- 分享卡片和页面栈:从分享进入时页面栈只有一页,
navigateBack会失败,必须兜底reLaunch到首页,上面组件代码已经处理。 - 深色模式:如果项目支持 darkmode,头部背景色要跟随
theme变化,别写死白色。
自定义头部本身不难,难的是把高度计算、返回兜底、滚动性能这几处细节一次性做对。把上面的组件放进项目里,后面每个页面传个 title 就能用,省下的时间够你多写两个业务页面了。
评论 (0)
还没有评论,快来抢沙发吧~