天天看點

Nissi商城首頁(一):仿唯品會的自定義頭部導航欄(完美)

一、前言

最近無意中打開“某品會”的小程式端,發現他們的頭部布局為啥和公司開發的小程式不一樣,自己的隻能文字居中,改變背景色。對方的竟然可以設定2行文字,字型也有大有小,感覺非常的高大上。

某品會小程式首頁:

Nissi商城首頁(一):仿唯品會的自定義頭部導航欄(完美)

接下來小編帶着問題去晚上查相關資料,發現微信7.0.0新增了解決不同螢幕頭部問題的終極辦法:

wx.getMenuButtonBoundingClientRect()      

有了這個神器支援擷取膠囊按鈕的高度,輕松解決“留海屏” 的适配問題。

二、開發

廢話不多說,直接撸代碼......

1、代碼目錄結構

├─components

│   └─navBar

│              navBar.js

│              navBar.json

│              navBar.wxml

│              navBar.wxss 

├─pages

│   └─index

│              index.js

│              index.json

│              index.wxml

│              index.wxss 

2、代碼片段

components/navBar/navBar.js部分

Component({
  options: {
    multipleSlots: true,
    addGlobalClass: true
  },
  data: {},
  pageLifetimes: {
    show: function() {
      if (getApp().globalSystemInfo.ios) {
        this.getSystemInfo();
        this.setStyle(); //設定樣式1
      }
    },
    hide: function() {}
  },
  methods: {
    setStyle: function(life) {
      const {
        statusBarHeight,
        navBarHeight,
        capsulePosition,
        navBarExtendHeight,
        ios,
        windowWidth
      } = getApp().globalSystemInfo;
      const { back, home, title } = this.data;
      let rightDistance = windowWidth - capsulePosition.right; //膠囊按鈕右側到螢幕右側的邊距
      let leftWidth = windowWidth - capsulePosition.left; //膠囊按鈕左側到螢幕右側的邊距

      let navigationbarinnerStyle = [
        `color: ${this.data.color}`,
        `background: ${this.data.background}`,
        `height:${navBarHeight + navBarExtendHeight}px`,
        `padding-top:${statusBarHeight}px`,
        `padding-right:${leftWidth}px`,
        `padding-bottom:${navBarExtendHeight}px`
      ].join(';');
      let navBarLeft = [];
      if ((back && !home) || (!back && home)) {
        navBarLeft = [`width:${capsulePosition.width}px`, `height:${capsulePosition.height}px`].join(';');
      } else if ((back && home) || title) {
        navBarLeft = [
          `width:${capsulePosition.width}px`,
          `height:${capsulePosition.height}px`,
          `margin-left:${rightDistance}px`
        ].join(';');
      } else {
        navBarLeft = [`width:auto`, `margin-left:0px`].join(';');
      }
      if (life === 'created') {
        this.data = {
          navigationbarinnerStyle,
          navBarLeft,
          navBarHeight,
          capsulePosition,
          navBarExtendHeight,
          ios
        };
      } else {
        this.setData({
          navigationbarinnerStyle,
          navBarLeft,
          navBarHeight,
          capsulePosition,
          navBarExtendHeight,
          ios
        });
      }
    },
    _showChange: function(value) {
      this.setStyle();
    },
    // 傳回事件
    back: function() {
      this.triggerEvent('back', { delta: this.data.delta });
    },
    home: function() {
      this.triggerEvent('home', {});
    },
    search: function() {
      this.triggerEvent('search', {});
    },
    getSystemInfo() {
      var app = getApp();
      if (app.globalSystemInfo && !app.globalSystemInfo.ios) {
        return app.globalSystemInfo;
      } else {
        let systemInfo = wx.getSystemInfoSync();
        let ios = !!(systemInfo.system.toLowerCase().search('ios') + 1);
        let rect;
        try {
          rect = wx.getMenuButtonBoundingClientRect ? wx.getMenuButtonBoundingClientRect() : null;
          if (rect === null) {
            throw 'getMenuButtonBoundingClientRect error';
          }
          //取值為0的情況  有可能width不為0 top為0的情況
          if (!rect.width || !rect.top || !rect.left || !rect.height) {
            throw 'getMenuButtonBoundingClientRect error';
          }
        } catch (error) {
          let gap = ''; //膠囊按鈕上下間距 使導航内容居中
          let width = 96; //膠囊的寬度
          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);
        }

        let navBarHeight = '';
        if (!systemInfo.statusBarHeight) {
          systemInfo.statusBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - 20;
          navBarHeight = (function() {
            let gap = rect.top - systemInfo.statusBarHeight;
            return 2 * gap + rect.height;
          })();

          systemInfo.statusBarHeight = 0;
          systemInfo.navBarExtendHeight = 0; //下方擴充4像素高度 防止下方邊距太小
        } else {
          navBarHeight = (function() {
            let gap = rect.top - systemInfo.statusBarHeight;
            return systemInfo.statusBarHeight + 2 * gap + rect.height;
          })();
          if (ios) {
            systemInfo.navBarExtendHeight = 4; //下方擴充4像素高度 防止下方邊距太小
          } else {
            systemInfo.navBarExtendHeight = 0;
          }
        }
        systemInfo.navBarHeight = navBarHeight; //導航欄高度不包括statusBarHeight
        systemInfo.capsulePosition = rect; //右上角膠囊按鈕資訊bottom: 58 height: 32 left: 317 right: 404 top: 26 width: 87 目前發現在大多機型都是固定值 為防止不一樣是以會使用動态值來計算nav元素大小
        systemInfo.ios = ios; //是否ios

        app.globalSystemInfo = systemInfo; //将資訊儲存到全局變量中,後邊再用就不用重新異步擷取了

        //console.log('systemInfo', systemInfo);
        return systemInfo;
      }
    }
  }
});      

components/navBar/navBar.wxml部分

<view class="lxy-nav-bar {{extClass}}" style="background: {{backgroundColorTop}};height: {{navBarHeight+ navBarExtendHeight}}px;">
    <view class="lxy-nav-bar__placeholder {{ios ? 'ios' : 'android'}}" style="padding-top: {{navBarHeight+ navBarExtendHeight}}px;visibility: hidden;"></view>
    <view class="lxy-nav-bar__inner {{ios ? 'ios' : 'android'}}" style="{{navigationbarinnerStyle}}{{displayStyle}}">
        <view class='lxy-nav-bar__left' style="{{navBarLeft}}">
            <block wx:if="{{back&&!home}}">
                <view bindtap="back" class="lxy-nav-bar__button lxy-nav-bar__btn_goback {{iconTheme}}"></view>
            </block>
            <block wx:if="{{!back&&home}}">
                <view bindtap="home" class="lxy-nav-bar__button lxy-nav-bar__btn_gohome {{iconTheme}}"></view>
            </block>
            <block wx:elif="{{back&&home}}">
                <view class="lxy-nav-bar__buttons {{ios ? 'ios' : 'android'}}" wx:if="{{home}}">
                    <view bindtap="back" class="lxy-nav-bar__button lxy-nav-bar__btn_goback {{iconTheme}}" wx:if="{{back}}"></view>
                    <view bindtap="home" class="lxy-nav-bar__button lxy-nav-bar__btn_gohome {{iconTheme}}"></view>
                </view>
            </block>
            <block wx:else>
                <slot name="left"></slot>
            </block>
        </view>
        <view class='lxy-nav-bar__right'>
            <slot name="right"></slot>
        </view>
    </view>
</view>      

pages/index/index.wxml部分

<view class="custom_head" style="height:{{navHeight}}px;">
  <view class="flex-row j_b" style="height:{{navObj}}px;padding-top:{{navTop}}px;padding-right:{{navObjWid+5}}px;">
    <view class="head_store text_title">
      {{store_name}}
    </view>
  </view>
</view>      

pages/index/index.js部分

const app = getApp();
Page({

  /**
   * 頁面的初始資料
   */
  data: {
    navHeight: app.globalData.navHeight, //導航欄高度
    navTop: app.globalData.navTop, //導航欄距頂部距離
    navObj: app.globalData.navObj, //膠囊的高度
    navObjWid: app.globalData.navObjWid, //膠囊寬度+距右距離
    storeList: [{
      name: '品牌特賣 100%正品'
    }],
  }
}      

此處省略樣式部分代碼塊......

三、結果

  • 通過微信官方API: getMenuButtonBoundingClientRect(),可以解決各類手機螢幕的适配問題。
  • 将算好的參數存儲在全局變量中,隻需一次計算就可以全局使用。

 四、源碼