小程序自定义导航自适应组件

1. 前言

小程序开发中,有时我们不需要自带的导航栏,那就需要自己写一个这样的导航栏。

2. 效果展示

在这里插入图片描述

3.文件目录结构

├── root
    │   ├── components
    │   │   └── navbar
    │   │       ├── index.js
    │   │       ├── index.wxml
    │   │       ├── index.json
    │   │       ├── index.wxss
    │   ├──pages
    │   │       ├── index.js
    │   │       ├── index.wxml
    │   │       ├── index.json
    │   │       ├── index.wxss
    └──── app.js

4.如何使用

pages/index

<navbar navbar-data="{{navbarData}}"></navbar>

Page({
  data: {
    navbarData: {
      showCapsule: 1, //是否显示头部左上角小房子 1显示 0 不显示
      showBack: 1, //是否显示返回 1显示 0不显示
      // showColor: 0, //navbar背景颜色 1展示 0不展示
      // navigationBarColor: 'yellow',
      bgTransparent: true, // 时候否透明
      iconColor: 'black',
      titleColor: 'black',
      title: '首页'
    },
  },
})

5. 实现代码

需要现在app.js启动的时候获取一些信息
app.js

// app.js
App({
  onLaunch() {
    const { statusBarHeight, titleBarHeight } = getStatusBarHeight()
    console.log(statusBarHeight, titleBarHeight)
    this.globalData.statusBarHeight = statusBarHeight;
    this.globalData.titleBarHeight = titleBarHeight;
  },
  globalData: {
    statusBarHeight: "",
    titleBarHeight: ""
  }
})
function getStatusBarHeight() {
  let res = wx.getSystemInfoSync()
  let customTitleBarHeight;
  try {
    const menuInfo = wx.getMenuButtonBoundingClientRect();
    if (equalVersion(res.version, "7.0.3")) {
      customTitleBarHeight =
        menuInfo.height + (menuInfo.top - res.statusBarHeight) * 2;
    } else {
      customTitleBarHeight = menuInfo.height + menuInfo.top * 2;
    }
  } catch (e) {
    customTitleBarHeight = 48;
  }
  if (res.model.indexOf("iPhone") !== -1) {
    customTitleBarHeight = 44;
  }
  return {
    statusBarHeight: res.statusBarHeight,
    titleBarHeight: customTitleBarHeight,
  }
};

function equalVersion(curV, reqV) {
  let arr1 = curV.split(".");
  let arr2 = reqV.split(".");

  let maxL = Math.max(arr1.length, arr2.length);
  let pos = 0;
  let diff = 0;

  while (pos < maxL) {
    diff = parseInt(arr1[pos]) - parseInt(arr2[pos]);
    console.log(diff, parseInt(arr1[pos]), parseInt(arr2[pos]));
    if (diff != 0) {
      break;
    }
    pos++;
  }
  if (diff > 0 || diff == 0) {
    //新版本、稳定版
    return 1;
  } else {
    // 旧版本
    return 0;
  }
}

下面是组件代码
component/navbar.js

/**
 * 自定义头部
 */

const app = getApp()

// 版本号比较函数
function compareVersion(v1, v2) {
    v1 = v1.split('.')
    v2 = v2.split('.')
    const len = Math.max(v1.length, v2.length)
    while (v1.length < len) {
        v1.push('0')
    }
    while (v2.length < len) {
        v2.push('0')
    }
    for (let i = 0; i < len; i++) {
        const num1 = parseInt(v1[i])
        const num2 = parseInt(v2[i])
        if (num1 > num2) {
            return 1
        } else if (num1 < num2) {
            return -1
        }
    }
    return 0
}


Component({
    properties: {
        navbarData: { //navbarData   由父页面传递的数据,变量名字自命名
            type: Object,
            value: {},
            observer: function (newVal, oldVal, changedPath) {
                if (newVal == null && typeof (oldVal) == 'object') {
                    try {
                        var _o = {};
                        var defaultData = {
                            statusBarHeight: '',
                            titleBarHeight: '',
                            navbarData: {
                                showCapsule: 1, //是否显示左上角小房子 1显示 0 不显示
                                showBack: 1, //是否显示返回 1显示 0不显示
                                showColor: 1, //navbar背景颜色 1蓝色 0白色
                                navigationBarColor: '#ffffff',
                                bgTransparent: false, // true 背景为透明 false 不透明(下面两个属性也不生效)
                                iconColor: 'black', // white 左侧icon颜色
                                titleColor: '#000000' // 可以指定title具体色值
                            }
                        }

                        _o['navibarData'] = defaultData['navbarData']
                        this.setData(_o);
                    } catch (e) {

                    }
                } else {
                    // var data = this.data;
                    var data = {};
                    var path = 'navibarData';
                    Object.keys(newVal).forEach(key => {
                        data[`${path}.${key}`] = newVal[key];
                    })
                    this.setData(data);
                }
            }
        }
    },

    data: {
        statusBarHeight: '',
        titleBarHeight: '',
        showNavbar: '',
        menuButtonInfo: {},
        navibarData: {
            showCapsule: 0, //是否显示左上角小房子
            showBack: 1, //是否显示返回
            showColor: 1, //navbar背景颜色,
            navigationBarColor: '#ffffff',
            bgTransparent: false, // true 背景为透明 false 不透明(下面两个属性也不生效)
            iconColor: 'black', // white 左侧icon颜色
            titleColor: '#000000' // 可以指定title具体色值
        }
    },
    attached: function () {
        if (wx.getSystemInfoSync().platform.toLowerCase().indexOf('devtools') != -1) {
            this.setData({
                showNavbar: true
            });
        } else {
            // 获取版本号
            const version = wx.getSystemInfoSync().version
            if (compareVersion(version, '7.0.0') >= 0) {
                // 版本号大于7.0.0时,显示自定义头部
                this.setData({
                    showNavbar: true
                })
            } else {
                //   // 版本号低于7.0.0时,隐藏自定义头部
                this.setData({
                    showNavbar: false
                })
            }
        }
        this.setData({
            statusBarHeight: app.globalData.statusBarHeight,
            titleBarHeight: app.globalData.titleBarHeight
        });
        //获取右上角胶囊的大小,为显示自定义icon做准备
        this.setData({
            menuButtonInfo: wx.getMenuButtonBoundingClientRect()
        });
    },
    ready() {
        try {
            const currentPages = getCurrentPages()
            const _o1 = {}
            //无论如何返回按钮出现的规则不变
            if (currentPages.length > 1) {
                _o1.showBack = true;
            } else {
                _o1.showBack = false;
            }
            this.setData({
                navibarData: {
                    ...this.data.navibarData,
                    ..._o1
                }
            })
        } catch (e) {

        }
    },
    methods: {
        //返回到首页
        _backhome(e) {
            wx.switchTab({
                url: '/pages/home/homepage',
            });
        },
        _backlast(e) {
            wx.navigateBack();
        },
    },
})

component/navbar.wxml

<view style="padding-top:{{navibarData.bgTransparent ? 0 : (statusBarHeight+titleBarHeight)}}px" wx:if='{{showNavbar}}'>
  <view wx:if='{{navibarData.bgTransparent}}' class="nav-wrap b-nav-wrap transparent-bg">
    <template is="titleBarContent" data="{{statusBarHeight,titleBarHeight,navibarData,menuButtonInfo}}"/>
  </view>
  <block wx:else>
    <view wx:if='{{navibarData.showColor}}' class="nav-wrap" style='background-color:{{navibarData && navibarData.navigationBarColor}};'>
      <template is="titleBarContent" data="{{statusBarHeight,titleBarHeight,navibarData,menuButtonInfo}}"/>
    </view>
    <view wx:else class="nav-wrap b-nav-wrap">
      <template is="titleBarContent" data="{{statusBarHeight,titleBarHeight,navibarData,menuButtonInfo}}"/>
    </view>  
  </block>
</view>

<template name="titleBarContent">
  <view class="status-bar" style="height:{{statusBarHeight}}px"></view>
    <view class="title-bar" style="height:{{titleBarHeight}}px">
      <view class="title-bar-group {{navibarData.showBack ^ navibarData.showCapsule ? 'only-group-icon' : navibarData.showBack && navibarData.showCapsule ? '' :'no-group-icon'}}">
        <view class="title-bar-icon" bindtap='_backlast' wx:if='{{navibarData.showBack}}'>
          <image wx:if="{{navibarData.bgTransparent&&navibarData.iconColor == 'white'}}" src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAApVBMVEUAAAD///+4/eNVAAAANnRSTlMACPH9+vbrsCsF5LwTDALcPTYhAd/Ik4J4ZFRMRkIyG9LNt6SZjGAvFg/Z1cKnoIdyaFomm2zHx4MpAAABcElEQVRYw+2YaXKCQBhEhwFRcEXBfd8xxpiYzP2PltR0pbzA+5FU8Q7w/tD012AqKir+B8F8ifp6qXM70DeJ3A8NStcZOs8J8rWa8m0HjK/Rle81YHwz63Xh2DCMQ++zMyh9O+fpQg94tZGv2WJ8WSrfMGF8x5rXRe9Qmg/OE/cYX7mVb91nfI+LfPU245tb+UbQ23ZXmmtTKM0350mhSj0X8hVnxrdM5btB5fKhNId3KM1vzmPnjK9dl+/yYHz9F/m2JeNbxPIdOtDbESnNR8NQKi5xht1y79usDMXBd31gMPa+rTqc8MidIxHk3MEUmeVOumjk3OgQrSs3i0Syd3Qav6LnMUYbIpoYQXWYBg3Zsso4ewdyZRy8VFaHHlyGoaYIwaogx5LWNTrntP/pjM/scxKz31D7xMBfedczVj9DGfMT+qWsMUvRi7m5Lfrr34zT9VNgD3swknFhMKY1/XPgWHad+zQkySIzFRUVf41vKdVQ6T8ZiacAAAAASUVORK5CYII=' mode='aspectFit' class='back-home-return'></image>
          <image wx:else src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAkFBMVEUAAAAzMzMzMzM1NTU6Ojo/Pz8zMzMzMzNEREQzMzMzMzM0NDRnZ2czMzMzMzMzMzMzMzM0NDQ0NDQ0NDQ2NjY3Nzf///8zMzMzMzMzMzMzMzMzMzMzMzM0NDQ0NDQzMzM0NDQ1NTU9PT0zMzMzMzMzMzMzMzM0NDQzMzM3Nzc3Nzc0NDQzMzM0NDQ2NjYzMzP3PMnqAAAAL3RSTlMA+vErDwj2sATs3TYC5uHQuU09MSETAdfJvaWak4J4ZEZCC8OgjGlgVRwYh3JaJmpD+NcAAAFhSURBVFjD7ZdbjoJAFAUbfKEIylsFBRREHbX3v7sxfTtxA/Uxk1ALqB8qtw9qZGTkfzBrEtQXB1p3oK929IcJpVtXWpPC6VZ825zxTVbiO8wYX+SK76QYTqJzI6i+g/hW0Pe478S3zxifH4iv8hjfMDc65wrV3GvDMmZr3qSM712Ib3FmfI2t+ZgzvqdjdPMbVHOnDSF0Uh+25suDrbmDjsvL1vyEav6xx6WBal6Ir3gzvtTWvJ0yvnYpvn7N+Bpb80C9baHxBb6CqOW43BVFb279TGGU5lqtOeHAFSOcQ+7BFHyXe9KFSciNDiG7cLNI8EpN13h1vo8xeiGcGjOmBTlo5Mqik0veAbrx15xu3A/IKSKvPTmWZF2jc072P9145H4nMfsPVWKNZ3s7xLDGvQqcikINjlkhXtKNpxvb+BR7D+352XmUMT+KsVUYN3N+EsWRfBovFYnX+mpkZOSv8Qvxhkf6bCYPxwAAAABJRU5ErkJggg==' mode='aspectFit' class='back-home-return'></image>
        </view>
        <view class="line" wx:if='{{navibarData.showBack && navibarData.showCapsule}}'></view>
        <view class="title-bar-icon" bindtap='_backhome' wx:if='{{navibarData.showCapsule}}'>
          <image wx:if="{{navibarData.bgTransparent&&navibarData.iconColor == 'white'}}" src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAXVBMVEUAAAD///9RKvvlAAAAHnRSTlMA9kNVhvo95i4VAuzgsHxjWhwH0Me9uaugcE82JiScUXyNAAABNElEQVRYw+3Wx3KEMBBF0YYZRYY4Oen/P9MuyviBEVNluhcOuuuno21T6tfW3e+dIOfOKgR1dlJeXoS+IhfysvBRlst6EPkeRFkPIt+DKOtB5HsQZT2IfA8i1+OL8PgiPDkRHl+EJyfC44vw5ER4fBGenAiPL8KTE+HxRXhyIjy+CE9OhMcX4cmJ8PgiPElRw1sr6rHnTWBnPKE2CNQS8ju+t/M0yh6Ki67WWpW+FAdLszZrwU3/PIEJ/O+gKrfvlUoKNJb6rBECm2HQCIFuGDghEIsEJvDvgAYL8z0wj89PWJzii5zitfF5jUW9eNXEO0Zvlg6DLnoDHWmpZzZfK02jtJovsictVqvZ+kaTbrM/VU0vsvvpunzQlx7ldLG39DKvr9uhqnEUyTXV5+SqPaVSP683QHRHUsVUZh0AAAAASUVORK5CYII=' mode='aspectFit' class='back-home'></image>
          <image wx:else src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAXVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5BxTwAAAAHnRSTlMA9kNVhvo95i4VAuzgsHxjWhwH0Me9uaugcE82JiScUXyNAAABNElEQVRYw+3Wx3KEMBBF0YYZRYY4Oen/P9MuyviBEVNluhcOuuuno21T6tfW3e+dIOfOKgR1dlJeXoS+IhfysvBRlst6EPkeRFkPIt+DKOtB5HsQZT2IfA8i1+OL8PgiPDkRHl+EJyfC44vw5ER4fBGenAiPL8KTE+HxRXhyIjy+CE9OhMcX4cmJ8PgiPElRw1sr6rHnTWBnPKE2CNQS8ju+t/M0yh6Ki67WWpW+FAdLszZrwU3/PIEJ/O+gKrfvlUoKNJb6rBECm2HQCIFuGDghEIsEJvDvgAYL8z0wj89PWJzii5zitfF5jUW9eNXEO0Zvlg6DLnoDHWmpZzZfK02jtJovsictVqvZ+kaTbrM/VU0vsvvpunzQlx7ldLG39DKvr9uhqnEUyTXV5+SqPaVSP683QHRHUsVUZh0AAAAASUVORK5CYII=' mode='aspectFit' class='back-home'></image>
        </view>
      </view>
      <view style="flex-grow:1;height:100%;background-color: transparent; display: flex; justify-content:{{navibarData.iconData.align?(navibarData.iconData.align==='right'?'flex-end':'flex-start'):'flex-start'}};">
      <view style="display: flex;align-items: center;height: 100%;">
      </view>
      </view>
      <view style="width:{{menuButtonInfo.width}}px;height:100%;background-color: transparent;"></view> 
      <view class='nav-titleContainer'>
        <view wx:if="{{navibarData.bgTransparent}}" class="nav-title" style="color:{{navibarData.titleColor}}">{{navibarData.title}}</view>
        <view wx:else class="nav-title">{{navibarData.title}}</view>
      </view>
    </view>
</template>

component/navbar.wxss

/* 顶部要固定定位 */
.nav-wrap {
  position: fixed;
  width: 100%;
  top: 0;
  z-index: 99;
  background: #ffffff;
}
/* 标题要居中 */
.nav-titleContainer {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  position: absolute;
  top:0;
  left:0;
  height: 100%;
  width: 100%;
  pointer-events: none;
}
.nav-title {
  margin: 0 180rpx;
  text-align: center;
  /* width: 50%; */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 36rpx;
  color: #000000;
  font-weight: 600;
  z-index: -1;
}

.back-home {
  width: 40rpx;
  height: 40rpx;
  cursor: pointer;
}
.back-home-return {
  width: 40rpx;
  height: 40rpx;
  cursor: pointer;
}

.title-bar-group {
  border: 1rpx solid rgba(255,255,255,0.25);
  background: rgba(0,0,0,0.12);
  border-radius: 32rpx;
  display: flex;
  height: 64rpx;
  width: 174rpx;
  box-sizing: border-box;
  align-items:center;
  overflow: hidden;
}

.title-bar-group.only-group-icon {
  border: none;
  background: transparent;
  width: 87rpx;
}

.title-bar-group.no-group-icon {
  border: none;
  background: transparent;
  width: 0rpx;
}

.title-bar-group.no-group-icon + .nav-titleContainer {
  justify-content:flex-start;
}

.title-bar-group.no-group-icon + .nav-titleContainer .nav-title {
  margin: 0 180rpx 0 12rpx;
  text-align:left;
}

.title-bar-icon {
  flex: auto;
  display:flex;
  align-items:center;
  justify-content:center;
  height:100%;
}

.line{
  height: 38rpx;
  border-left: 1px solid rgba(255,255,255,0.12);
}

.title-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 0 20rpx;
  position: relative;
}

.b-nav-wrap.nav-wrap {
  background: #fff;
}
.b-nav-wrap .nav-title {
  color: #111;
}
.b-nav-wrap .title-bar-group {
  border: 1rpx solid rgba(0,0,0,0.12);
  background: #ffffff;
}
.b-nav-wrap .line{
  border-left: 1px solid rgba(0,0,0,0.12);
}

.b-nav-wrap .title-bar-group.only-group-icon {
  border: none;
  background: transparent;
  width: 87rpx;
}

.b-nav-wrap .title-bar-group.no-group-icon {
  border: none;
  background: transparent;
  width: 0rpx;
}

.b-nav-wrap.nav-wrap.transparent-bg,
.b-nav-wrap.nav-wrap.transparent-bg .title-bar-group {
  background: transparent;
}

6. 原理分析

在这里插入图片描述

wx.getSystemInfoSync(); // 获取到状态栏的信息
wx.getMenuButtonBoundingClientRect(); // 获取胶囊的信息

function getStatusBarHeight() {
  let res = wx.getSystemInfoSync()
  let customTitleBarHeight;
  try {
    const menuInfo = wx.getMenuButtonBoundingClientRect(); // 获取胶囊的信息
    if (equalVersion(res.version, "7.0.3")) {
      customTitleBarHeight =
        menuInfo.height + (menuInfo.top - res.statusBarHeight) * 2;
    } else {
      customTitleBarHeight = menuInfo.height + menuInfo.top * 2;
    }
  } catch (e) {
    customTitleBarHeight = 48;
  }
  if (res.model.indexOf("iPhone") !== -1) {
    customTitleBarHeight = 44;
  }
  return {
    statusBarHeight: res.statusBarHeight, // 状态栏的高度
    titleBarHeight: customTitleBarHeight, // title的高度
  }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值