一句话:H5 嵌在 App WebView 里时,状态栏高度只有原生知道——用 JSBridge 取
statusBarHeight,再给 NavBar 加padding-top。
写在前面
用 Vant 开发的 H5 页面嵌入原生 App 时,顶部导航栏必须避开状态栏(Status Bar)和刘海屏安全区,否则标题会被系统栏遮挡。纯 H5 无法直接读状态栏高度,需要 App 通过 WebViewJavascriptBridge 暴露接口。
读完你能:封装 bridge.js;实现 JS 调 Native / Native 调 JS;在 NavBar 组件里动态设置 paddingTop。
核心内容
1. bridge.js:初始化 WebViewJavascriptBridge
Android 与 iOS 初始化方式不同,需分别处理:
javascript
const isAndroid = navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Adr') > -1
const isiOS = !!navigator.userAgent.match(/(i[^;]+;( U;)? CPU.+Mac OS X/)
function setupWebViewJavascriptBridge(callback) {
if (isAndroid) {
if (window.WebViewJavascriptBridge) {
callback(window.WebViewJavascriptBridge)
} else {
document.addEventListener('WebViewJavascriptBridgeReady', () => {
callback(window.WebViewJavascriptBridge)
}, false)
}
sessionStorage.phoneType = 'android'
}
if (isiOS) {
if (window.WebViewJavascriptBridge) {
return callback(window.WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback]
const WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(() => document.documentElement.removeChild(WVJBIframe), 0)
sessionStorage.phoneType = 'ios'
}
}
setupWebViewJavascriptBridge((bridge) => {
if (isAndroid) {
bridge.init((message, responseCallback) => {
responseCallback({ 'Javascript Responds': 'Wee!' })
})
}
})
export default {
callHandler(name, data, callback) {
setupWebViewJavascriptBridge((bridge) => {
bridge.callHandler(name, data, callback)
})
},
registerHandler(name, callback) {
setupWebViewJavascriptBridge((bridge) => {
bridge.registerHandler(name, (data, responseCallback) => {
callback(data, responseCallback)
})
})
},
isAndroid,
isiOS
}
要点:Android 必须 bridge.init;iOS 通过隐藏 iframe 触发 scheme 加载 Bridge。
2. 封装 JS ↔ App 调用
javascript
function callApp(params, callback) {
let data = {}
for (const key in params) {
data = { funname: key, value: params[key] }
}
this.$bridge.callHandler('callFunction', data, (res) => {
if (res) {
res = JSON.parse(res)
res.code = Number(res.code)
if (res.code === -10000) {
this.$toast('APP 版本过低,请升级后再使用')
} else if (res.code !== 0 && res.msg) {
this.$toast(res.msg)
}
}
callback && callback(res)
})
}
function appCall(callback) {
this.$bridge.registerHandler('appCallJS', (res, responseCallback) => {
res = res && JSON.parse(res)
if (res && +res.value.code === 0) {
switch (res.funname) {
case 'token_mobile':
sessionStorage.setItem('token', res.value.data.token)
break
case 'userInfo_mobile':
sessionStorage.setItem('user', JSON.stringify(res.value.data))
break
}
}
callback && callback(res)
responseCallback && responseCallback({ ok: true })
})
}
约定:funname 标识方法名,Native 返回 JSON 字符串,H5 统一 JSON.parse。
3. NavBar 组件:动态 padding-top
vue
<template>
<van-nav-bar
left-arrow
:border="border"
:title="title"
:fixed="fixed"
:z-index="99"
:style="{ paddingTop: fixed ? statusBarHeight + 'px' : undefined }"
@click-left="clickLeft"
/>
</template>
<script>
export default {
props: {
fixed: { type: Boolean, default: true },
backApp: { type: String, default: 'yes' },
title: { type: String, default: '' }
},
data() {
return { statusBarHeight: 0 }
},
created() {
this.callApp({ statusBarHeight_mobile: '' }, (res) => {
if (res.code === 0) {
this.statusBarHeight = res.data.dpptHeight || 44
}
})
},
methods: {
clickLeft() {
if (this.backApp === 'no') {
this.$router.go(-1)
} else {
this.callApp({ back_mobile: '' })
}
}
}
}
</script>
fixed 导航栏脱离文档流,必须用 padding-top(而非 margin)把内容推到状态栏下方;默认值 44 作 iOS 经典高度兜底。
4. 现代替代:env(safe-area-inset-top)
纯 H5 或支持 CSS 环境变量的 WebView,可优先:
css
.nav-bar {
padding-top: env(safe-area-inset-top);
}
需在 viewport 加 viewport-fit=cover。与 JSBridge 方案可并存:JS 取值覆盖 CSS 兜底。
踩坑
- Bridge 未就绪就 callHandler:必须等
WebViewJavascriptBridgeReady或 iOS iframe 回调后再调。 - Android/iOS 返回字段不一致:统一 Native 契约,H5 侧做字段映射。
JSON.parse未 try/catch:Native 返回非 JSON 会直接抛错。- fixed NavBar 遮挡内容:页面主体需额外
padding-top: navHeight + statusBarHeight。 - 低版本 App 返回
-10000时应提示升级,避免静默失败。
小结
- WebView H5 的布局要听 Native:状态栏高度、返回栈由 App 决定。
- 封装 Bridge 层,业务只调
callApp/registerHandler。 - NavBar 用动态
paddingTop;新项目可结合safe-area-inset。 - 返回逻辑区分「路由后退」与「关闭 WebView 回 App」。


全部评论(0)