【uni-app】自定义导航栏以及状态栏,胶囊按钮位置信息的获取
概念
什么是安全区域 getSafeAreaInsets()
安全区域是屏幕上不会被以下系统元素遮挡的区域:
- 状态栏 :显示时间、电量、信号等信息的顶部区域
- 导航栏 :底部的虚拟按键区域(Android)
- 刘海屏 :iPhone X 及以后机型的顶部刘海区域
- 圆角 :设备屏幕的圆角部分
- 胶囊按钮 :微信小程序右上角的胶囊按钮区域
插件参考
UniPages
需要将navigationStyle
设置为custom
新建hook文件
useNavigation.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 = resstatusBarHeight.value = res.statusBarHeight || 0},fail: () => {// 降级处理statusBarHeight.value = 20 // iOS 默认状态栏高度},})// 获取胶囊按钮信息(仅小程序环境)// #ifdef MPtry {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-WEIXINconst 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-WEIXINreturn systemInfo.value.safeAreaInsets// #endif}return {statusBarHeight,menuButtonInfo,systemInfo,navigationBarHeight,contentTop,initNavigation,getSafeAreaInsets,}
}/*** 同步获取导航栏高度的工具函数* 适用于需要立即获取高度的场景*/
export function getNavigationHeight() {let statusBarHeight = 0let 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.heightconst menuTop = menuButton.topconst menuBottom = menuTop + menuHeightconst paddingBottom = menuTop - statusBarHeightnavigationBarHeight = menuBottom + paddingBottom}else {navigationBarHeight = statusBarHeight + 44}// #endif// #ifndef MP-WEIXINnavigationBarHeight = statusBarHeight + 44// #endif}catch (error) {console.warn('获取导航栏高度失败:', error)statusBarHeight = 20navigationBarHeight = 64}return {statusBarHeight,navigationBarHeight,contentTop: navigationBarHeight,}
}
图片标注
演示
如果使用的是unibest这个架子去开发的可以直接使用下面的代码拷贝进页面进行演示查看,也就是上面图片展示的数据,可以在微信开发者工具中切换不同的机型去查看数据,做一个更好的理解
<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"><!-- 状态栏标注区域 --><viewclass="status-bar-annotation":style="{height: `${statusBarHeight}px`,}"><view class="annotation-label"><text class="annotation-text">状态栏 {{ statusBarHeight }}px</text></view></view><!-- 自定义导航栏 --><viewclass="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><!-- 胶囊按钮标注(仅微信小程序) --><viewv-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><!-- 内容区域分割线 --><viewclass="content-divider":style="{top: `${contentTop}px`,}"><text class="divider-text">内容区域开始</text></view><!-- 内容区域 --><viewclass="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 了解,我们只需要提需求,然后让他给出方案即可,强大的很!!!!