129 lines
4.2 KiB
TypeScript
129 lines
4.2 KiB
TypeScript
import { useMemo } from 'react';
|
||
import Taro from '@tarojs/taro';
|
||
|
||
export type NavBarMetrics = {
|
||
/** 状态栏高度(刘海/灵动岛上方) */
|
||
statusBarHeight: number;
|
||
/** 导航栏总高度 = 状态栏 + 内容区 */
|
||
navBarHeight: number;
|
||
/** 标题栏内容区高度(与胶囊对齐) */
|
||
navContentHeight: number;
|
||
/** 右侧留白,避免与微信胶囊按钮重叠 */
|
||
navBarPaddingRight: number;
|
||
/** 左侧留白,与右侧对称以实现标题视觉居中 */
|
||
navBarPaddingLeft: number;
|
||
};
|
||
|
||
const H5_FALLBACK: NavBarMetrics = {
|
||
statusBarHeight: 0,
|
||
navBarHeight: 56,
|
||
navContentHeight: 56,
|
||
/** 避开微信内置浏览器右上角 ··· / 设置入口(约一颗胶囊宽) */
|
||
navBarPaddingRight: 96,
|
||
navBarPaddingLeft: 16,
|
||
};
|
||
|
||
const WEAPP_FALLBACK: NavBarMetrics = {
|
||
statusBarHeight: 20,
|
||
navBarHeight: 64,
|
||
navContentHeight: 44,
|
||
navBarPaddingRight: 96,
|
||
navBarPaddingLeft: 96,
|
||
};
|
||
|
||
/** 计算小程序自定义导航栏尺寸(对齐微信胶囊按钮) */
|
||
export function getNavBarMetrics(): NavBarMetrics {
|
||
if (process.env.TARO_ENV === 'h5') {
|
||
// H5:在微信浏览器内额外避让右上角菜单;非微信保持较小右侧留白
|
||
const inWechat =
|
||
typeof navigator !== 'undefined' && /MicroMessenger/i.test(navigator.userAgent || '');
|
||
return {
|
||
...H5_FALLBACK,
|
||
navBarPaddingRight: inWechat ? 96 : 16,
|
||
};
|
||
}
|
||
|
||
try {
|
||
const win = Taro.getWindowInfo?.() ?? Taro.getSystemInfoSync();
|
||
const menu = Taro.getMenuButtonBoundingClientRect();
|
||
const statusBarHeight = win.statusBarHeight ?? WEAPP_FALLBACK.statusBarHeight;
|
||
const navContentHeight =
|
||
menu.height > 0
|
||
? (menu.top - statusBarHeight) * 2 + menu.height
|
||
: WEAPP_FALLBACK.navContentHeight;
|
||
const navBarHeight = statusBarHeight + navContentHeight;
|
||
const navBarPaddingRight =
|
||
menu.width > 0
|
||
? Math.max(win.windowWidth - menu.left + 8, 16)
|
||
: WEAPP_FALLBACK.navBarPaddingRight;
|
||
|
||
return {
|
||
statusBarHeight,
|
||
navBarHeight,
|
||
navContentHeight,
|
||
navBarPaddingRight,
|
||
navBarPaddingLeft: navBarPaddingRight,
|
||
};
|
||
} catch {
|
||
return WEAPP_FALLBACK;
|
||
}
|
||
}
|
||
|
||
export function useNavBarMetrics(): NavBarMetrics {
|
||
return useMemo(() => getNavBarMetrics(), []);
|
||
}
|
||
|
||
/** 仅注入 CSS 变量,供 PageShell / sticky 子元素使用(不加 height/padding) */
|
||
export function pageShellCssVars(metrics: NavBarMetrics): Record<string, string> {
|
||
return {
|
||
'--nav-bar-height': `${metrics.navBarHeight}px`,
|
||
'--nav-content-height': `${metrics.navContentHeight}px`,
|
||
'--nav-status-bar-height': `${metrics.statusBarHeight}px`,
|
||
'--nav-padding-right': `${metrics.navBarPaddingRight}px`,
|
||
'--nav-padding-left': `${metrics.navBarPaddingLeft}px`,
|
||
};
|
||
}
|
||
|
||
/** 顶栏自身样式:statusBar padding + 总高 + CSS 变量 */
|
||
export function navBarStyle(metrics: NavBarMetrics): Record<string, string | number> {
|
||
return {
|
||
paddingTop: `${metrics.statusBarHeight}px`,
|
||
height: `${metrics.navBarHeight}px`,
|
||
...pageShellCssVars(metrics),
|
||
};
|
||
}
|
||
|
||
/** Tab 顶栏内容行:左右留白(标题单独全屏居中) */
|
||
export function tabNavContentStyle(metrics: NavBarMetrics): Record<string, string | number> {
|
||
return {
|
||
height: `${metrics.navContentHeight}px`,
|
||
paddingLeft: '20px',
|
||
paddingRight: `${metrics.navBarPaddingRight}px`,
|
||
boxSizing: 'border-box',
|
||
};
|
||
}
|
||
|
||
/** 子页/内页顶栏:标题全屏居中,内容区单独留白 */
|
||
export function subPageNavBarStyle(metrics: NavBarMetrics): Record<string, string | number> {
|
||
const pagePad = 20;
|
||
return {
|
||
paddingTop: `${metrics.statusBarHeight}px`,
|
||
height: `${metrics.navBarHeight}px`,
|
||
'--nav-bar-height': `${metrics.navBarHeight}px`,
|
||
'--nav-content-height': `${metrics.navContentHeight}px`,
|
||
'--nav-status-bar-height': `${metrics.statusBarHeight}px`,
|
||
'--nav-padding-left': `${pagePad}px`,
|
||
'--nav-padding-right': `${metrics.navBarPaddingRight}px`,
|
||
};
|
||
}
|
||
|
||
/** 子页顶栏内容行:左侧页边距 + 右侧避让胶囊 */
|
||
export function subPageNavContentStyle(metrics: NavBarMetrics): Record<string, string | number> {
|
||
return {
|
||
height: `${metrics.navContentHeight}px`,
|
||
paddingLeft: '20px',
|
||
paddingRight: `${metrics.navBarPaddingRight}px`,
|
||
boxSizing: 'border-box',
|
||
};
|
||
}
|