navigationStyle: "custom"是统一关闭各端原生导航栏并使内容顶到viewport顶部的前提,H5需重置page边距和伪元素,App端需配置immersed:true启用沉浸式,小程序端须用env.safeArea.top动态适配安全区。

pages.json 里设 navigationStyle 为 "custom" 是前提
不设这个,其他操作基本白搭。它才是真正让页面内容从 viewport 顶部开始渲染的开关,而不是只“隐藏文字”。H5 和 App 端都认这个配置;小程序端会强制启用自定义导航栏——如果你没写组件,顶部就真成白板了,所以得配套处理。
常见错误是只改 titleNView: false 或只调 navigationBarTitleText,这些只是样式层开关,DOM 结构和布局占位还在。
-
navigationStyle: "custom"必须写在pages.json对应页面的style下,全局配置也行,但单页更可控 - App 端建议同时加
"app-plus": { "titleNView": false },双保险关闭原生导航栏绘制 - 小程序端必须自己实现一个顶部导航栏组件,否则交互区域缺失、视觉断裂
H5 端要清空 page 的默认边距和伪元素
即使开了 navigationStyle: "custom",H5 页面顶部仍可能有空白,根源是浏览器默认样式 + UniApp 注入的 page::before 占位。
在页面级 CSS(比如 App.vue 的 <style> 或页面 <style scoped>)里加:
page {
margin: 0;
padding: 0;
}
page::before {
display: none;
}
注意:不能只靠 padding-top: 0,因为框架注入的内联样式可能带 !important,直接重置 margin/padding 更可靠。
App 端需启用沉浸式状态栏
仅设 navigationStyle: "custom" 和 titleNView: false 后,Android 和 iOS 仍可能保留状态栏高度的占位(比如 25px 或 44px),内容被下压。
必须在 pages.json 页面 style 中补充:
"app-plus": {
"statusbar": {
"immersed": true
}
}
这个字段告诉原生层:状态栏区域允许内容穿透,不预留空白。它只对 App 端生效,H5 和小程序忽略。
- 不加
immersed: true,即使 DOM 顶到了顶部,WebView 渲染时仍会按“有状态栏”留空 - 如果用了自定义状态栏背景色,可额外加
"background": "#ffffff"避免黑色块
小程序端安全区 top 值不能硬编码 statusBarHeight
很多开发者用 uni.getSystemInfoSync().statusBarHeight 计算导航栏位置,结果在刘海屏或 iOS 15+ 上偏移严重。因为小程序容器渲染的导航栏区域 ≠ 状态栏本身高度,还包含阴影、分割线、安全区上边界等。
唯一可靠方式是读取 env.safeArea.top:
onLoad() {
const env = uni.getEnv && uni.getEnv();
if (env && env.safeArea && env.safeArea.top) {
this.safeAreaTop = env.safeArea.top;
} else {
// fallback:H5 或降级场景用 statusBarHeight
const info = uni.getSystemInfoSync();
this.safeAreaTop = info.statusBarHeight || 0;
}
}
然后用这个值动态设置自定义导航栏的 padding-top 或容器 margin-top。别在 created 或 mounted 里读,onLoad 才能确保 env 已注入。
真正麻烦的不是“怎么关”,而是“关完之后各端怎么接住内容”。H5 要清 DOM 边距,App 要开 immersed,小程序得靠 env.safeArea.top 动态适配——三者逻辑不同,缺一不可。


















