背景
在做小程序時,關於默認導航欄,我們遇到了以下的問題:
- Android、IOS 手機對於頁面 title 的展示不一致,安卓 title 的顯示不居中
- 頁面的 title 只支持純文本級別的樣式控制,不能夠做更豐富的 title 效果
- 左上角的事件無法監聽、定制
- 路由導航單一,只能夠返回上一頁,深層級頁面的返回不夠友好
探索
小程序自定義導航欄已開放許久>>了解一下,相信不少小伙伴已使用過這個功能,同時不少小伙伴也會發現一些坑:
- 機型多如牛毛:自定義導航欄高度在不同機型始終無法達到視覺上的統一
- 導航欄中內容怎么都不垂直居中對齊,更別說適配所有手機
- 調皮的膠囊按鈕:導航欄元素(文字,圖標等)怎么也對不齊那該死的膠囊按鈕
- 各種尺寸的全面屏,奇怪的劉海屏,簡直要抓狂
一探究竟
為了搞明白原理,我先去翻了官方文檔,>>飛機,點過去是不是很驚喜,很意外,通篇大文盡然只有最下方的一張圖片與這個問題有關,並且啥也看不清,汗汗汗...
我特意找了一張圖片來
分析上圖,我得到如下信息:
- Android 跟 iOS 有差異,表現在頂部到膠囊按鈕之間的距離差了 6pt
- 膠囊按鈕高度為 32pt, iOS 和 Android 一致
動手分析
我們寫一個狀態欄,通過 wx.getSystemInfoSync().statusBarHeight 設置高度
Android:
iOS:
可以看出,iOS 膠囊按鈕與狀態欄之間距離為:4px, Android 為 8px,是不是所有手機都是這種情況呢?
答案是:蘋果手機確實都是 4px,安卓大部分都是 7 和 8 也會有其他的情況(可以自己打印 getSystemInfo 驗證)如何快速便捷算出這個高度,請接着往下看
如何計算
導航欄分為狀態欄和標題欄,只要能算出每台手機的導航欄高度問題就迎刃而解
- 導航欄高度 = 膠囊按鈕高度 + 狀態欄到膠囊按鈕間距 * 2 + 狀態欄高度
注:由於膠囊按鈕是原生組件,為表現一致,其單位在各種手機中都為 px,所以我們自定義導航欄的單位都必需是 px(切記不能用 rpx),才能完美適配。
解決問題
現在我們明白了原理,可以利用膠囊按鈕的位置信息和 statusBarHeight 高度動態計算導航欄的高度,貼一個實現此功能最重要的方法
let systemInfo = wx.getSystemInfoSync();
let rect = wx.getMenuButtonBoundingClientRect ? wx.getMenuButtonBoundingClientRect() : null; //膠囊按鈕位置信息
wx.getMenuButtonBoundingClientRect();
let navBarHeight = (function() { //導航欄高度
let gap = rect.top - systemInfo.statusBarHeight; //動態計算每台手機狀態欄到膠囊按鈕間距
return 2 * gap + rect.height;
})();
gap 信息就是不同的手機其狀態欄到膠囊按鈕間距,具體更多代碼實現和使用 demo 請移步下方代碼倉庫,代碼中還會有輸入框文字跳動解決辦法,安卓手機輸入框文字飛出解決辦法,左側按鈕邊框太粗解決辦法等等
膠囊信息報錯和獲取不到
問題就在於 getMenuButtonBoundingClientRect 這個方法,在某些機子和環境下會報錯或者獲取不到,對於此種情況完美可以模擬一個膠囊位置出來
try {
rect = Taro.getMenuButtonBoundingClientRect ? Taro.getMenuButtonBoundingClientRect() : null;
if (rect === null) {
throw 'getMenuButtonBoundingClientRect error';
}
//取值為0的情況
if (!rect.width) {
throw 'getMenuButtonBoundingClientRect error';
}
} catch (error) {
let gap = ''; //膠囊按鈕上下間距 使導航內容居中
let width = 96; //膠囊的寬度,android大部分96,ios為88
if (systemInfo.platform === 'android') {
gap = 8;
width = 96;
} else if (systemInfo.platform === 'devtools') {
if (ios) {
gap = 5.5; //開發工具中ios手機
} else {
gap = 7.5; //開發工具中android和其他手機
}
} else {
gap = 4;
width = 88;
}
if (!systemInfo.statusBarHeight) {
//開啟wifi的情況下修復statusBarHeight值獲取不到
systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20;
}
rect = {
//獲取不到膠囊信息就自定義重置一個
bottom: systemInfo.statusBarHeight + gap + 32,
height: 32,
left: systemInfo.windowWidth - width - 10,
right: systemInfo.windowWidth - 10,
top: systemInfo.statusBarHeight + gap,
width: width
};
console.log('error', error);
console.log('rect', rect);
}
以上代碼主要是借鑒了拼多多的默認值寫法,android 機子中 gap 值大部分為 8,ios 都為 4,開發工具中 ios 為 5.5,android 為 7.5,這樣處理之后自己模擬一個膠囊按鈕的位置,這樣在獲取不到膠囊信息的情況下,可保證絕大多數機子完美顯示導航頭
吐槽
這么重要的問題,官方盡然沒有提供解決方案...竟然提供了一張看不清的圖片???
網上有很多 ios 設置 44,android 設置 48,還有根據不同的手機型號設置不同高度,通過長時間的開發和嘗試,本人發現以上方案並不完美,並且 bug 很多
代碼庫
- Taro 組件gitHub 地址詳細用法請參考 README
- 原生組件 npm 構建版本gitHub 地址詳細用法請參考 README
- 原生組件簡易版gitHub 地址詳細用法請參考 README
- 由於本人精力有限,目前只計划發布維護好這 2 種組件,其他組件請自行修改代碼,有問題請聯系
備注
- 上方 2 種組件在最下方 30 多款手機測試情況表現良好
- iPhone 手機打電話和開熱點導致導航欄樣式錯亂,問題已經解決啦,請去 demo 里測試,這里特別感謝 moments 網友提出的問題
- 本文章並無任何商業性質,如有侵權請聯系本人修改或刪除
- 文章少量部分內容是本人查詢搜集而來
- 如有問題可以下方留言討論,微信 zhijunxh
比較
斗魚:
虎牙:
微博:
酷狗:
知乎:
知乎是這里邊做的最好的,但是我個人認為有幾個可以優化的小問題
- 打電話或者開啟熱點導致樣式錯落,這也是大部門小程序的問題
- 導航欄下邊距太小,看起來不舒服
- 搜索框距離 2 側按鈕組距離不對等
- 自定義返回和 home 按鈕中的豎線顏色重了,並且感覺太粗
如果您看到了此篇文章,請趕快修改自己的代碼,並運用在實踐中吧
掃碼體驗我的小程序:
測試信息
手機型號 | 膠囊位置信息 | statusBarHeight | 測試情況 |
---|---|---|---|
iPhoneX | 80 32 281 369 48 88 | 44 | 通過 |
iPhone8 plus | 56 32 320 408 24 88 | 20 | 通過 |
iphone7 | 56 32 281 368 24 87 | 20 | 通過 |
iPhone6 plus | 56 32 320 408 24 88 | 20 | 通過 |
iPhone6 | 56 32 281 368 24 87 | 20 | 通過 |
HUAWEI SLA-AL00 | 64 32 254 350 32 96 | 24 | 通過 |
HUAWEI VTR-AL00 | 64 32 254 350 32 96 | 24 | 通過 |
HUAWEI EVA-AL00 | 64 32 254 350 32 96 | 24 | 通過 |
HUAWEI EML-AL00 | 68 32 254 350 36 96 | 29 | 通過 |
HUAWEI VOG-AL00 | 65 32 254 350 33 96 | 25 | 通過 |
HUAWEI ATU-TL10 | 64 32 254 350 32 96 | 24 | 通過 |
HUAWEI SMARTISAN OS105 | 64 32 326 422 32 96 | 24 | 通過 |
XIAOMI MI6 | 59 28 265 352 31 87 | 23 | 通過 |
XIAOMI MI4LTE | 60 32 254 350 28 96 | 20 | 通過 |
XIAOMI MIX3 | 74 32 287 383 42 96 | 35 | 通過 |
REDMI NOTE3 | 64 32 254 350 32 96 | 24 | 通過 |
REDMI NOTE4 | 64 32 254 350 32 96 | 24 | 通過 |
REDMI NOTE3 | 55 28 255 351 27 96 | 20 | 通過 |
REDMI 5plus | 67 32 287 383 35 96 | 28 | 通過 |
MEIZU M571C | 65 32 254 350 33 96 | 25 | 通過 |
MEIZU M6 NOTE | 62 32 254 350 30 96 | 22 | 通過 |
MEIZU MX4 PRO | 62 32 278 374 30 96 | 22 | 通過 |
OPPO A33 | 65 32 254 350 33 96 | 26 | 通過 |
OPPO R11 | 58 32 254 350 26 96 | 18 | 通過 |
VIVO Y55 | 64 32 254 350 32 96 | 24 | 通過 |
HONOR BLN-AL20 | 64 32 254 350 32 96 | 24 | 通過 |
HONOR NEM-AL10 | 59 28 265 352 31 87 | 24 | 通過 |
HONOR BND-AL10 | 64 32 254 350 32 96 | 24 | 通過 |
HONOR duk-al20 | 64 32 254 350 32 96 | 24 | 通過 |
SAMSUNG SM-G9550 | 64 32 305 401 32 96 | 24 | 通過 |
360 1801-A01 | 64 32 254 350 32 96 | 24 | 通過 |