评论

[打怪升级]小程序自定义头部导航栏“完美”解决方案

解决各类手机顶部自定义导航不对齐问题

为什么要做这个?

主要是在项目中,智酷君发现的一些问题

  • 一些页面是通过扫码和订阅消息访问后,没有直接可以点击去首页的,需要添加一个home链接
  • 需要添加自定义搜索功能
  • 需要自定义一些功能按钮

其实,第一个问题,在最近的微信版本更新中已经优化了,通过 小程序模板消息 过来的,系统会自动加上home按钮,但对于其他的访问方式则没有支持~

一个不大不小的问题:两边ICON不对齐问题

智酷君之前尝试了各种解决方法,发现有一个问题,就是现在手机屏幕太多种多样,有 传统头部、宽/窄刘海屏、水滴屏等等,无法八门,很多解决方案都无法解决特殊头部,系统**“胶囊按钮”** 和 自定义按钮在Android屏幕可能有 几像素不对齐 的问题(强迫症的噩梦)。

下面分享下一个相对比较完善的解决方案:

小程序代码段DEMO

Link: https://developers.weixin.qq.com/s/cuUaCimT72cH
ID: cuUaCimT72cH

智酷君做了一个demo代码段,方便大家直接用IDE工具查看源码~

页面配置

1、页面JSON配置
{
  "usingComponents": {
    "NavComponent": "/components/nav/common"  //以插件的方式引入
  },
  "navigationStyle": "custom"  //自定义头部需要设置
}

如果需要自定义头部,需要设置navigationStyle为 “custom”

2、页面代码
<!-- home 类型的菜单 -->
<NavComponent v-title="自定义头部" bind:commonNavAttr="commonNavAttr"></NavComponent>
<!-- 搜索菜单 -->
<NavComponent is-search="true" bind:commonNavAttr="commonNavAttr"></NavComponent>

可以在自定义导航标签上添加属性配置来设置功能,具体按照实际需要来

3、目录结构
│
├─components
│  └─nav
│          common.js
│          common.json
│          common.wxml
│          common.wxss
│
├─images
│      back.png
│      home.png
│
└─index
        index.js
        index.json
        index.wxml
        index.wxss
        search.js
        search.json
        search.wxml
        search.wxss

仅供参考

插件对应的JS部分

components/nav/common.js部分
const app = getApp();
Component({
  properties: {
    vTitle: {
      type: String,
      value: ""
    },
    isSearch:{
      type: Boolean,
      value: false
    }
  },
  data: {
    haveBack: true, // 是否有返回按钮,true 有 false 没有 若从分享页进入则没有返回按钮
    statusBarHeight: 0, // 状态栏高度
    navbarHeight: 0, // 顶部导航栏高度
    navbarBtn: { // 胶囊位置信息
      height: 0,
      width: 0,
      top: 0,
      bottom: 0,
      right: 0
    },
    cusnavH: 0, //title高度
  },
  // 微信7.0.0支持wx.getMenuButtonBoundingClientRect()获得胶囊按钮高度
  attached: function () {
    if (!app.globalData.systeminfo) {
      app.globalData.systeminfo = wx.getSystemInfoSync();
    }
    if (!app.globalData.headerBtnPosi) app.globalData.headerBtnPosi = wx.getMenuButtonBoundingClientRect();
    console.log(app.globalData)
    let statusBarHeight = app.globalData.systeminfo.statusBarHeight // 状态栏高度
    let headerPosi = app.globalData.headerBtnPosi // 胶囊位置信息
    console.log(statusBarHeight)
    console.log(headerPosi)
    let btnPosi = { // 胶囊实际位置,坐标信息不是左上角原点
      height: headerPosi.height,
      width: headerPosi.width,
      top: headerPosi.top - statusBarHeight, // 胶囊top - 状态栏高度
      bottom: headerPosi.bottom - headerPosi.height - statusBarHeight, // 胶囊bottom - 胶囊height - 状态栏height (胶囊实际bottom 为距离导航栏底部的长度)
      right: app.globalData.systeminfo.windowWidth - headerPosi.right // 这里不能获取 屏幕宽度,PC端打开小程序会有BUG,要获取窗口高度 - 胶囊right
    }
    let haveBack;
    if (getCurrentPages().length != 1) { // 当只有一个页面时,并且是从分享页进入
      haveBack = false;
    } else {
      haveBack = true;
    }
    var cusnavH = btnPosi.height + btnPosi.top + btnPosi.bottom // 导航高度
    console.log( app.globalData.systeminfo.windowWidth, headerPosi.width)
    this.setData({
      haveBack: haveBack, // 获取是否是通过分享进入的小程序
      statusBarHeight: statusBarHeight,
      navbarHeight: headerPosi.bottom + btnPosi.bottom, // 胶囊bottom + 胶囊实际bottom
      navbarBtn: btnPosi,
      cusnavH: cusnavH
    });
    //将实际nav高度传给父类页面
    this.triggerEvent('commonNavAttr',{
      height: headerPosi.bottom + btnPosi.bottom
    });
  },
  methods: {
    _goBack: function () {
      wx.navigateBack({
        delta: 1
      });
    },
    bindKeyInput:function(e){
      console.log(e.detail.value);
    }
  }
})

解决不同屏幕头部不对齐问题的终极办法是 wx.getMenuButtonBoundingClientRect()
这个方法从微信7.0.0开始支持,通过这个方法我们可以获取到右边系统胶囊的top、height、right等属性,这样无论是水滴屏、刘海屏、异形屏,都能完美对齐右边系统默认的胶囊bar,完美治愈强迫症~

APP.js 部分
//app.js
App({
  /**
   * 加载页面
   * @param {*} options 
   */
  onShow: function (options) {
   
  },
  onLaunch: async function () {
    let self = this;

    //设置默认分享
    this.globalData.shareData = {
      title: "智酷方程式"
    }

    // this.getSysInfo();
  },
  globalData: {
    //默认分享文案
    shareData: {},
    qrCodeScene: false, //二维码扫码进入传参
    systeminfo: false,   //系统信息
    headerBtnPosi: false,  //头部菜单高度
  }
});

将获取的参数存储在一个全局变量globalData中,可以减少反复调用的性能消耗。

插件HTML部分

<view class="custom_nav" style="height:{{navbarHeight}}px;">
    <view class="custom_nav_box" style="height:{{navbarHeight}}px;">
        <view class="custom_nav_bar" style="top:{{statusBarHeight}}px; height:{{cusnavH}}px;">
            <!-- 搜索部分-->
            <block wx:if="{{isSearch}}">
                <input class="navSearch"
                    style="height:{{navbarBtn.height-2}}px;line-height:{{navbarBtn.height-4}}px; top:{{navbarBtn.top+1}}px; left:{{navbarBtn.right}}px; border-radius:{{navbarBtn.height/2}}px;"
                    maxlength="10" bindinput="bindKeyInput" placeholder="输入文字搜索" />
            </block>
            <!-- HOME 部分-->
            <block wx:else>
                <view class="custom_nav_icon {{!haveBack||'borderLine'}}"
                    style="height:{{navbarBtn.height}}px;line-height:{{navbarBtn.height-2}}px; top:{{navbarBtn.top}}px; left:{{navbarBtn.right}}px; border-radius:{{navbarBtn.height/2}}px;">
                    <view wx:if="{{haveBack}}" class="icon-back" bindtap='_goBack'>
                        <image src='/images/back.png' mode='aspectFill' class='back-pre'></image>
                    </view>
                    <view wx:if="{{haveBack}}" class='navbar-v-line'></view>
                    <view class="icon-home">
                        <navigator class="home_a" url="/pages/home/index" open-type="switchTab">
                            <image src='/images/home.png' mode='aspectFill' class='back-home'></image>
                        </navigator>
                    </view>
                </view>
                <view class="nav_title" style="height:{{cusnavH}}px; line-height:{{cusnavH}}px;">
                    {{vTitle}}
                </view>
            </block>
        </view>
    </view>
</view>

主要是对几种状态的判断和定位的计算。

插件CSS部分

/* components/nav/test.wxss */
.custom_nav {
    width: 100%;
    background: #3a7dd7;
    position: relative;
    z-index: 99999;
}
.custom_nav_box {
    position: fixed;
    width: 100%;
    background: #3a7dd7;
    z-index: 99999;
    border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
}
.custom_nav_bar {
    position: relative;
    z-index: 9;
}
.custom_nav_box .nav_title {
    font-size: 28rpx;
    color: #fff;
    text-align: center;
    position: absolute;
    max-width: 360rpx;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
    z-index: 1;
}
.custom_nav_box .custom_nav_icon {
    position:absolute;
    z-index: 2;
    display: inline-block;
    border-radius: 50%;
    vertical-align: top;
    font-size:0;
    box-sizing: border-box;
}
.custom_nav_box .custom_nav_icon.borderLine {
    border: 1rpx solid rgba(255, 255, 255, 0.3);
    background: rgba(0, 0, 0, 0.1);
}
.navbar-v-line {
    width: 1px;
    margin-top: 14rpx;
    height: 32rpx;
    background-color: rgba(255, 255, 255, 0.3);
    display: inline-block;
    vertical-align: top;
}
.icon-back {
    display: inline-block;
    width: 74rpx;
    padding-left: 20rpx;
    vertical-align: top;
    /* margin-top: 12rpx;
    vertical-align: top; */
    height: 100%;
}
.icon-home {
    /* margin-top: 8rpx;
    vertical-align: top; */
    display: inline-block;
    width: 80rpx;
    text-align: center;
    vertical-align: top;
    height: 100%;
}
.icon-home .home_a {
    height: 100%;
    display: inline-block;
    vertical-align: top;
    width: 35rpx;
}
.custom_nav_box .back-pre,
.custom_nav_box .back-home {
    width: 35rpx;
    height: 35rpx;
    vertical-align: middle;
}
.navSearch {
  width: 200px;
  background: #fff;
  font-size: 14px;
  position: absolute;
  padding: 0 20rpx;
  z-index: 9;
}

总结:

  • 通过微信API: getMenuButtonBoundingClientRect(),结果各类手机屏幕的适配问题
  • 将算好的参数存储在全局变量中,一次计算全局使用,爽YY~

往期回顾:

最后一次编辑于  2021-09-13  
点赞 14
收藏
评论

7 个评论

  • 张有釜
    张有釜
    2020-07-16

     wx.getMenuButtonBoundingClientRect()

    这个API 在第一次载入小程序的时候 有概率 返回undefined 需要兼容处理 IOS 安卓都有出现

    2020-07-16
    赞同 1
    回复 3
    • 游戏人生
      游戏人生
      2020-07-16
      我暂时没遇到过,你的内核版本是?
      2020-07-16
      回复
    • 张有釜
      张有釜
      2020-07-17回复游戏人生
      这个是官方bug 全版本都有 官方说修复了 但实际情况是 还未修复
      官方的折中建议是延迟100MS 或 在返回undefined的情况下 重新获取一次
      2020-07-17
      回复
    • 游戏人生
      游戏人生
      2020-07-17回复张有釜
      嗯嗯,我做了初始化判断
      2020-07-17
      回复
  • 完美天使😊
    完美天使😊
    2022-07-10

    非常棒!解决了大问题:https://blog.csdn.net/zhenghhgz/article/details/125691093

    2022-07-10
    赞同
    回复
  • 李有理
    李有理
    2020-11-11

    在下拉刷新时,tabbar会跟着一起移动。怎么解决一下啊

    2020-11-11
    赞同
    回复 2
    • 游戏人生
      游戏人生
      2020-11-13
      自定义头部是fixed状态吗? 我们用fixed解决,不出现 三个点,改为loading加载数据,让用户感知到就行了 。 参考 nike和adidas的小程序~
      2020-11-13
      回复
    • 相信自己
      相信自己
      2022-01-22
      用sticky定位
      2022-01-22
      回复
  • lxy
    lxy
    2020-04-20

    楼主请教一下,设置enablePullDownRefresh属性设置为true

    "enablePullDownRefresh"true
    

    自定义头部跟随一起滚动,请问有什么处理方案么

    2020-04-20
    赞同
    回复 1
    • 游戏人生
      游戏人生
      2020-11-13
      自定义头部是fixed状态吗? 我们用fixed解决,不出现 三个点,改为loading加载数据,让用户感知到就行了
      2020-11-13
      回复
  • 张玉峰
    张玉峰
    2020-03-20

    楼主是否遇到过小程序进入后台后过长期打开首页高度塌陷呢(偶尔性)?我是在app.js中获取的在onshow中给了判断然后给组件进行的传值,跟您的组件内获取有些许不一样。

    2020-03-20
    赞同
    回复 2
    • kk🙄
      kk🙄
      2022-12-05
      我也出现了这个问题 请问后面你怎么解决的呀
      2022-12-05
      回复
    • GG·bond
      GG·bond
      04-01
      我也遇到了高度塌陷 ,请问怎么解决的呢
      04-01
      回复
  • 칡길릭
    칡길릭
    2019-11-11

    为什么你这么优秀

    2019-11-11
    赞同
    回复
  • 2019-11-05

    谢谢 楼主 解决我多年的疑惑

    2019-11-05
    赞同
    回复 3
登录 后发表内容