【uni-app】自定义导航栏以及状态栏,胶囊按钮位置信息的获取

概念

什么是安全区域 getSafeAreaInsets()

安全区域是屏幕上不会被以下系统元素遮挡的区域:

  • 状态栏 :显示时间、电量、信号等信息的顶部区域
  • 导航栏 :底部的虚拟按键区域(Android)
  • 刘海屏 :iPhone X 及以后机型的顶部刘海区域
  • 圆角 :设备屏幕的圆角部分
  • 胶囊按钮 :微信小程序右上角的胶囊按钮区域

插件参考

UniPages

需要将navigationStyle设置为custom

新建hook文件

useNavigation.ts

ts 复制代码
import { computed, ref } from 'vue'

/**
 * 导航栏相关信息的 Hook
 * 用于获取状态栏高度、胶囊按钮信息、导航栏高度等
 */
export function useNavigation() {
  // 状态栏高度(单位:px)
  const statusBarHeight = ref(0)
  // 胶囊按钮信息(仅微信小程序)
  const menuButtonInfo = ref<UniApp.GetMenuButtonBoundingClientRectRes | null>(null)
  // 系统信息
  const systemInfo = ref<UniApp.GetSystemInfoResult | null>(null)

  // 计算导航栏高度 
  // 计算这个导航栏的高度有很多种方案
  const navigationBarHeight = computed(() => {
    if (!menuButtonInfo.value || !statusBarHeight.value) {
      // 默认导航栏高度(当无法获取胶囊信息时)
      return statusBarHeight.value + 44
    }

    const menuHeight = menuButtonInfo.value.height // 胶囊按钮的高度
    const menuTop = menuButtonInfo.value.top // 胶囊按钮距离顶部的距离
    const menuBottom = menuTop + menuHeight // 胶囊按钮底部距离

    // 导航栏高度 = 胶囊按钮底部距离 + 胶囊按钮距离导航栏底部的距离
    // 通常胶囊按钮距离导航栏底部的距离等于距离顶部的距离
    const paddingBottom = menuTop - statusBarHeight.value // 胶囊按钮距离导航栏底部的距离
    return menuBottom + paddingBottom
  })

  // 计算内容区域的顶部偏移量(状态栏 + 导航栏)
  const contentTop = computed(() => {
    return navigationBarHeight.value
  })

  // 初始化获取系统信息
  const initNavigation = () => {
    // 获取系统信息
    uni.getSystemInfo({
      success: (res) => {
        systemInfo.value = res
        statusBarHeight.value = res.statusBarHeight || 0
      },
      fail: () => {
        // 降级处理
        statusBarHeight.value = 20 // iOS 默认状态栏高度
      },
    })

    // 获取胶囊按钮信息(仅小程序环境)
    // #ifdef MP
    try {
      const menuButton = uni.getMenuButtonBoundingClientRect()
      if (menuButton && menuButton.width > 0) {
        menuButtonInfo.value = menuButton
      }
    }
    catch (error) {
      console.warn('获取胶囊按钮信息失败:', error)
    }
    // #endif
  }

  // 获取安全区域信息
  const getSafeAreaInsets = () => {
    if (!systemInfo.value)
      return null

    // #ifdef MP-WEIXIN
    const windowInfo = uni.getWindowInfo() // 获取窗口信息
    return windowInfo.safeArea
      ? {
          top: windowInfo.safeArea.top, // 顶部安全边距(避开状态栏、刘海等)
          right: windowInfo.windowWidth - windowInfo.safeArea.right, // 右侧安全边距(避开圆角等)
          bottom: windowInfo.windowHeight - windowInfo.safeArea.bottom, // 底部安全边距(避开导航栏、Home指示器等
          left: windowInfo.safeArea.left, // 左侧安全边距(避开圆角等)
        }
      : null
    // #endif

    // #ifndef MP-WEIXIN
    return systemInfo.value.safeAreaInsets
    // #endif
  }

  return {
    statusBarHeight,
    menuButtonInfo,
    systemInfo,
    navigationBarHeight,
    contentTop,
    initNavigation,
    getSafeAreaInsets,
  }
}

/**
 * 同步获取导航栏高度的工具函数
 * 适用于需要立即获取高度的场景
 */
export function getNavigationHeight() {
  let statusBarHeight = 0
  let navigationBarHeight = 44 // 默认导航栏高度

  try {
    // 获取系统信息
    const systemInfo = uni.getSystemInfoSync()
    statusBarHeight = systemInfo.statusBarHeight || 0

    // #ifdef MP-WEIXIN
    // 获取胶囊按钮信息
    const menuButton = uni.getMenuButtonBoundingClientRect()
    if (menuButton && menuButton.width > 0) {
      const menuHeight = menuButton.height
      const menuTop = menuButton.top
      const menuBottom = menuTop + menuHeight
      const paddingBottom = menuTop - statusBarHeight
      navigationBarHeight = menuBottom + paddingBottom
    }
    else {
      navigationBarHeight = statusBarHeight + 44
    }
    // #endif

    // #ifndef MP-WEIXIN
    navigationBarHeight = statusBarHeight + 44
    // #endif
  }
  catch (error) {
    console.warn('获取导航栏高度失败:', error)
    statusBarHeight = 20
    navigationBarHeight = 64
  }

  return {
    statusBarHeight,
    navigationBarHeight,
    contentTop: navigationBarHeight,
  }
}

图片标注

演示

如果使用的是unibest这个架子去开发的可以直接使用下面的代码拷贝进页面进行演示查看,也就是上面图片展示的数据,可以在微信开发者工具中切换不同的机型去查看数据,做一个更好的理解

html 复制代码
<route lang="jsonc" type="page">
{
  "style": {
    "navigationStyle": "custom",
    "navigationBarTitleText": "导航栏高度示例"
  }
}
</route>

<script setup lang="ts">
import { getNavigationHeight, useNavigation } from '@/hooks'

// 使用 Hook 方式(响应式)
const {
  statusBarHeight,
  menuButtonInfo,
  navigationBarHeight,
  contentTop,
  initNavigation,
  getSafeAreaInsets,
} = useNavigation()

// 使用同步方式(立即获取)
const syncHeights = getNavigationHeight()

// 组件挂载时初始化
onMounted(() => {
  initNavigation()

  console.log('同步获取的高度信息:', syncHeights)
})

// 监听高度变化
watch([statusBarHeight, navigationBarHeight], ([newStatusHeight, newNavHeight]) => {
  console.log('状态栏高度:', newStatusHeight)
  console.log('导航栏高度:', newNavHeight)
  console.log('内容区顶部偏移:', contentTop.value)
})
</script>

<template>
  <view class="demo-page">
    <!-- 状态栏标注区域 -->
    <view
      class="status-bar-annotation"
      :style="{
        height: `${statusBarHeight}px`,
      }"
    >
      <view class="annotation-label">
        <text class="annotation-text">
          状态栏 {{ statusBarHeight }}px
        </text>
      </view>
    </view>

    <!-- 自定义导航栏 -->
    <view
      class="custom-navbar box-border"
      :style="{
        height: `${navigationBarHeight}px`,
        paddingTop: `${statusBarHeight}px`,
      }"
    >
      <!-- 导航栏内容区域标注 -->
      <view class="navbar-content-annotation">
        <text class="navbar-annotation-text">
          导航栏内容区 44px
        </text>
      </view>

      <view class="navbar-content">
        <text class="navbar-title">
          自定义导航栏
        </text>
      </view>

      <!-- 胶囊按钮标注(仅微信小程序) -->
      <view
        v-if="menuButtonInfo"
        class="capsule-annotation"
        :style="{
          width: `${menuButtonInfo.width}px`,
          height: `${menuButtonInfo.height}px`,
          top: `${menuButtonInfo.top - statusBarHeight}px`,
          right: `${menuButtonInfo.right}px`,
        }"
      >
        <view class="capsule-label">
          <text class="capsule-text">
            胶囊按钮<br>
            {{ menuButtonInfo.width }}×{{ menuButtonInfo.height }}px<br>
            距顶: {{ menuButtonInfo.top }}px
          </text>
        </view>
      </view>
    </view>

    <!-- 导航栏总高度标注 -->
    <view class="navbar-height-annotation">
      <view class="height-indicator">
        <view class="height-line" />
        <text class="height-text">
          总高度: {{ navigationBarHeight }}px
        </text>
      </view>
    </view>

    <!-- 内容区域偏移标注 -->
    <view class="content-offset-annotation">
      <view class="offset-indicator">
        <view class="offset-line" />
        <text class="offset-text">
          内容区偏移: {{ contentTop }}px
        </text>
      </view>
    </view>

    <!-- 内容区域分割线 -->
    <view
      class="content-divider"
      :style="{
        top: `${contentTop}px`,
      }"
    >
      <text class="divider-text">
        内容区域开始
      </text>
    </view>

    <!-- 内容区域 -->
    <view
      class="content"
      :style="{
        paddingTop: `${contentTop + 24}px`,
      }"
    >
      <view class="info-card">
        <text class="card-title">
          导航栏信息
        </text>

        <view class="info-item">
          <text class="label">
            状态栏高度:
          </text>
          <text class="value">
            {{ statusBarHeight }}px
          </text>
        </view>

        <view class="info-item">
          <text class="label">
            导航栏高度:
          </text>
          <text class="value">
            {{ navigationBarHeight }}px
          </text>
        </view>

        <view class="info-item">
          <text class="label">
            内容区顶部偏移:
          </text>
          <text class="value">
            {{ contentTop }}px
          </text>
        </view>

        <view v-if="menuButtonInfo" class="info-item">
          <text class="label">
            胶囊按钮信息:
          </text>
          <text class="value">
            宽: {{ menuButtonInfo.width }}px,
            高: {{ menuButtonInfo.height }}px,
            顶部: {{ menuButtonInfo.top }}px
          </text>
        </view>
      </view>

      <view class="info-card">
        <text class="card-title">
          同步获取的信息
        </text>

        <view class="info-item">
          <text class="label">
            状态栏高度:
          </text>
          <text class="value">
            {{ syncHeights.statusBarHeight }}px
          </text>
        </view>

        <view class="info-item">
          <text class="label">
            导航栏高度:
          </text>
          <text class="value">
            {{ syncHeights.navigationBarHeight }}px
          </text>
        </view>
      </view>
    </view>
  </view>
</template>

<style lang="scss" scoped>
.demo-page {
  min-height: 100vh;
  background-color: #f5f5f5;
}

// 状态栏标注样式
.status-bar-annotation {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  background: rgba(255, 0, 0, 0.3);
  border-bottom: 2px dashed #ff0000;
  z-index: 1001;
  display: flex;
  align-items: center;
  justify-content: center;

  .annotation-label {
    background: rgba(255, 0, 0, 0.8);
    padding: 2px 8px;
    border-radius: 4px;

    .annotation-text {
      color: white;
      font-size: 12px;
      font-weight: 600;
    }
  }
}

.custom-navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  z-index: 1000;

  // 导航栏内容区域标注
  .navbar-content-annotation {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 44px;
    background: rgba(0, 255, 0, 0.2);
    border: 2px dashed #00ff00;
    display: flex;
    align-items: flex-start;
    justify-content: flex-start;
    padding: 2px 8px;

    .navbar-annotation-text {
      background: rgba(0, 255, 0, 0.8);
      color: white;
      font-size: 10px;
      font-weight: 600;
      padding: 1px 4px;
      border-radius: 2px;
    }
  }

  .navbar-content {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 44px;
    position: relative;
    z-index: 2;

    .navbar-title {
      color: white;
      font-size: 18px;
      font-weight: 600;
    }
  }

  // 胶囊按钮标注
  .capsule-annotation {
    position: absolute;
    background: rgba(255, 165, 0, 0.3);
    border: 2px dashed #ffa500;
    border-radius: 16px;
    display: flex;
    align-items: center;
    justify-content: center;

    .capsule-label {
      position: absolute;
      top: -60px;
      right: 0;
      background: rgba(255, 165, 0, 0.9);
      padding: 4px 8px;
      border-radius: 4px;
      white-space: nowrap;

      .capsule-text {
        color: white;
        font-size: 10px;
        font-weight: 600;
        line-height: 1.2;
      }
    }
  }
}

// 导航栏总高度标注
.navbar-height-annotation {
  position: fixed;
  left: 10px;
  top: 0;
  z-index: 1002;
  height: v-bind('`${navigationBarHeight}px`');

  .height-indicator {
    display: flex;
    align-items: flex-start;
    height: 100%;

    .height-line {
      width: 2px;
      height: 100%;
      background: #ff6b6b;
      margin-right: 8px;
    }

    .height-text {
      background: rgba(255, 107, 107, 0.9);
      color: white;
      font-size: 12px;
      font-weight: 600;
      padding: 4px 8px;
      border-radius: 4px;
      white-space: nowrap;
      margin-top: 50%;
      transform: translateY(-50%);
    }
  }
}

// 内容区域偏移标注
.content-offset-annotation {
  position: fixed;
  right: 10px;
  top: 0;
  z-index: 1002;
  height: v-bind('`${contentTop}px`');

  .offset-indicator {
    display: flex;
    align-items: flex-start;
    height: 100%;

    .offset-line {
      width: 2px;
      height: 100%;
      background: #9c27b0;
      margin-left: 8px;
    }

    .offset-text {
      background: rgba(156, 39, 176, 0.9);
      color: white;
      font-size: 12px;
      font-weight: 600;
      padding: 4px 8px;
      border-radius: 4px;
      white-space: nowrap;
      margin-top: 50%;
      transform: translateY(-50%);
      margin-right: 8px;
    }
  }
}

// 内容区域分割线
.content-divider {
  position: fixed;
  left: 0;
  right: 0;
  height: 2px;
  background: #e91e63;
  z-index: 1001;
  display: flex;
  align-items: center;
  justify-content: center;

  .divider-text {
    background: #e91e63;
    color: white;
    font-size: 12px;
    font-weight: 600;
    padding: 4px 12px;
    border-radius: 12px;
    position: absolute;
    top: -12px;
  }
}

.content {
  padding: 20px;

  .info-card {
    background: white;
    border-radius: 12px;
    padding: 20px;
    margin-bottom: 20px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);

    .card-title {
      font-size: 18px;
      font-weight: 600;
      color: #333;
      margin-bottom: 16px;
      display: block;
    }

    .info-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 0;
      border-bottom: 1px solid #f0f0f0;

      &:last-child {
        border-bottom: none;
      }

      .label {
        color: #666;
        font-size: 14px;
      }

      .value {
        color: #333;
        font-size: 14px;
        font-weight: 500;
      }
    }
  }
}
</style>

以上代码有大模型生成,做了部分的更改跟注释,不得不说大模型强大的很,很多uniapp 的 api 并不是很了解,但是 ai 了解,我们只需要提需求,然后让他给出方案即可,强大的很!!!!

相关推荐
咸虾米1 小时前
微信小程序服务端api签名,安全鉴权模式介绍,通过封装方法实现请求内容加密与签名
vue.js·微信小程序·uni-app
Ratten3 小时前
使用 uniapp 实现的扫雷游戏
uni-app
2501_915921433 小时前
iOS 应用上架多环境实战,Windows、Linux 与 Mac 的不同路径
android·ios·小程序·https·uni-app·iphone·webview
yede6 小时前
uniapp - 自定义页面的tabBar
vue.js·uni-app
谢泽豪7 小时前
解决 uniapp 修改index.html文件不生效的问题
前端·uni-app
00后程序员张8 小时前
iOS 应用上架常见问题与解决方案,多工具组合的实战经验
android·ios·小程序·https·uni-app·iphone·webview
奶糖 肥晨18 小时前
解决 UniApp 自定义弹框被图片或 Canvas 覆盖的 Bug
uni-app·bug
荷花微笑18 小时前
HBuilderX升级,Vue2 scss 预编译器默认已由 node-sass 更换为 dart-sass
uni-app·css3
2501_916007471 天前
iOS App 上架实战 从内测到应用商店发布的全周期流程解析
android·ios·小程序·https·uni-app·iphone·webview