4.4 TabBar 与自定义导航栏 本节导读:TabBar 与导航栏是用户对"骨架"的第一观感。本节讲原生 TabBar 的配置与限制、custom-tab-bar 的自由度与代价,以及 navigationStyle custom 之后必须处理的安全区问题,全部落在商城"底部四口 + 沉浸首页"这个真实需求上。 原生 TabBar:先吃透限制再谈替代 pages.json 里的 tabBar 配置把底部骨架一次定死:list 二到五项、每项配 pagePath 与文本、可选图标两态。原生方案的优势实打实——渲染在原生层、切换不闪、性能最好,小程序端还自带切入动画。
本节导读:TabBar 与导航栏是用户对"骨架"的第一观感。本节讲原生 TabBar 的配置与限制、custom-tab-bar 的自由度与代价,以及 navigationStyle custom 之后必须处理的安全区问题,全部落在商城"底部四口 + 沉浸首页"这个真实需求上。
pages.json 里的 tabBar 配置把底部骨架一次定死:list 二到五项、每项配 pagePath 与文本、可选图标两态。原生方案的优势实打实——渲染在原生层、切换不闪、性能最好,小程序端还自带切入动画。它的限制同样实打实,商城评审时逐条撞过:
{ "tabBar": { "color": "#8a8f99", "selectedColor": "#e93b3d", "borderStyle": "black", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/tab/home.png", "selectedIconPath": "static/tab/home-on.png" }, { "pagePath": "pages/category/category", "text": "分类" }, { "pagePath": "pages/cart/cart", "text": "购物车" }, { "pagePath": "pages/user/user", "text": "我的" } ] } }
限制一:图标必须用本地图片且不支持字体图标与网络图,设计给出的 SVG 图标要先导出成 PNG 双态;限制二:中途改 TabBar 项属于编译期变更,动态增减 Tab 靠它做不到;限制三:角标与红点只能调 setTabBarBadge 这类 API 设置,样式由宿主统一,设计稿上的"个性小红点"落不了地。撞了这三条,再考虑 custom-tab-bar。
把 tabBar 配置里 custom 打开后,Tab 完全由你的组件渲染:支持字体图标、支持中间凸起的发布按钮、支持运营配置的动态入口。代价清单也要摊开:每端要做一次适配(微信端有专门的 custom-tab-bar 目录约定,App 与 H5 端用普通组件模拟);切换动画与原生层无 mountains 关系,闪烁感要靠优化组件结构压制;页面上拉触底高度要多算一条 Tab 高度,否则最后一条数据被遮住。商城的取舍是务实派:常规四口用原生 TabBar,只在"中间大加号"这类原生做不了的设计出现时,才为该端开 custom。骨架的原生性是性能资产,不为一个角标样式花掉它。
首页要做成头图沉浸式(图片顶到状态栏下),navigationStyle custom 一开,原生导航栏消失,状态栏高度与胶囊按钮区域全部由你负责。跨端的正确做法是量出来而不是写死:
// 在自定义导航栏组件里量安全区 const sys = uni.getSystemInfoSync(); this.statusBarHeight = sys.statusBarHeight; // 状态栏高度,各端都有值 // #ifdef MP-WEIXIN // 微信端还要避开右上角胶囊按钮 const rect = wx.getMenuButtonBoundingClientRect(); this.navBarHeight = (rect.top - this.statusBarHeight) * 2 + rect.height; // #endif // #ifndef MP-WEIXIN this.navBarHeight = 44; // 其余端的常规导航栏高度 // #endif
两个容易漏的细节。其一,H5 端不要盲目沉浸:浏览器自带地址栏占顶部,沉浸头图在部分浏览器会顶到地址栏底下,H5 端常见的折中是保留普通导航或给头图加顶部留白。其二,底部安全区(全面屏的home indicator)会盖住吸底购买栏,给 fixed 元素加 constant/env(safe-area-inset-bottom) 的内边距,一行声明换三端不翻车。安全区这类量出来的值,放进全局样式变量或 Vuex 状态一次量好处处用,比每页各量一遍干净。
购物车数量要在 TabBar 图标上显示角标,这一小块接合是本节知识的实战收口。做法分三步:store 的数量变化处调用 uni.setTabBarBadge 写入角标;数量归零时调用 removeTabBarBadge 而不是写空串;Tab 页的 onShow 里同步一次,防止全局状态被其他端修改后角标不同步。两个端差要留意:setTabBarBadge 在 H5 端支持良好,但部分老浏览器兼容模式下样式有偏差,重要的"必达"提示别只押在角标上;自定义 TabBar 场景下这几个 API 失效,角标改由自己的组件渲染,状态源仍是同一份 store。接合代码不长,但它是"全局状态驱动界面骨架"的最小完整样例,值得通读一遍。
运营对导航栏的需求往往逐步升级:先要改标题,再要加按钮,最后要整条自定义。给出每级需求的推荐做法,避免一步到位的过度设计。只改标题与颜色:页面级 style 配置就够,原生导航栏保留。要加固定按钮:App 端用原生标题栏按钮,微信端放页面内右上角区域的自定义元素,不必拆掉整条导航。头图沉浸或整条重构:才动 navigationStyle custom 与安全区方案。三级需求三级做法,原生导航栏能保留就保留——它替你承担了返回手势、标题滚动联动、无障碍适配一整串细节,拆掉容易,补齐很难。
骨架两端(顶与底)都处理妥当,下一节解决"页面太多包太大":分包与预加载。