一、前言
uni-app
应用开发过程中,需要应用内嵌 H5 页面。
现如今,各大 APP 平台都有属于自己的小程序体系,各种各样的应用都可直接内嵌在 APP 中实现一站式体验。使用uniapp
开发的 APP 如何实现这样的功能呢?答案就是内嵌web-view
。
二、 码上谈兵
📢 注意事项:APP 中有vue
页面及nvue
页面,两种页面均可内嵌web-view
,但两种页面的表现不一:
每个vue
页面,其实都是一个webview
,而vue
页面里的web-view
组件,其实是webview
里的一个子webview
。这个子webview
被 append 到父webview
上。
vue
页面会自动铺满整个页面,接收web-view
页面通信使用的是@message
;
nvue
页面则需要指定页面宽高,接收web-view
页面通信使用的是@onPostMessage
;
app-vue
下web-view
组件不支持自定义样式,而v-show
的本质是改变组件的样式。即组件支持v-if
而不是支持v-show
。
<web-view>
组件默认铺满全屏并且层级高于前端组件。App 端想调节大小或在其上覆盖内容需使用 plus 规范。
H5
端的web-view
其实是被转为iframe
运行,使用的是当前的浏览器;
App
端,iOS
,是分为UIWebview
和WKWebview
的,2.2.5+起默认为WKWebview
;
nvue
web-view
必须指定样式宽高;
App
网页向应用 postMessage
为实时消息;
app-nvue
web-view
默认没有大小,可以通过样式设置大小,如果想充满整个窗口,设置 flex: 1
即可,标题栏不会自动显示 web-view
页面中的 title
。如果想充满整个窗口且想要显示标题推荐使用 vue
页面的 web-view
(默认充满屏幕不可控制大小), 想自定义 web-view
大小使用 nvue web-view
;
由APP
通知web-view
页面,无论是vue
页面还是nvue
页面,只有evalJS
方法,但调用姿势不一致。
vue 页面调用:
<template>
<web-view :src="url" @message="message" :webview-styles="webviewStyles" @onPostMessage="handlePostMessage"></web-view>
<button class="button" @click="evalJs">evalJs(改变webview背景颜色)</button>
</template>
...
<script>
export default {
data() {
return {
webviewStyles: {
progress: {
color: '#FF3333'
}
}
}
}
onLoad() {
this.currentWebview = this.$mp.page.$getAppWebview();
this.currentWebview.children()[0].evalJS('xxx');
},
methods: {
// webview向外部发送消息,APP接收并处理
handlePostMessage: function(data) {
console.log("接收到消息:" + JSON.stringify(data.detail));
},
// 调用 webview 内部逻辑
evalJs: function() {
this.$refs.webview.evalJs("document.body.style.background ='#00FF00'");
}
}
}
</script>
复制代码
nvue 页面调用:
<template>
<web-view ref="webview" :src="url" @message="message"></web-view>
</template>
...
<script>
export default {
xxx,
onReady() {
this.currentWebview = this.$refs.webview;
this.currentWebview.evalJS('xxx');
}
}
</script>
复制代码
nvue
页面中使用的web-view
页面是无法调用plus API
的,vue
页面是可以控制外部web-view
页面是否可用plus API
,其他事项具体参考web-view | uni-app官网。
三、适配问题剖析
Android、iOS
平台顶部导航栏的表现不一:因内嵌web-view
应用需要全屏显示,标题栏也交由内嵌应用自定义,故在pages.json
中定义页面的时候,须将页面的titleNView
设置为false
。但即便如此,其表现仍然不一致,表现为Android
端的页面起始位置是手机屏幕的最顶部(含状态栏在内),iOS
端则是从状态栏之下开始渲染页面,为了抹平差异,可使用webview
的setStyle
统一样式。
APP 端设置:
export default {
onReady() {
this.currentWebview = this.$mp.page.$getAppWebview();
// 提前计算好屏幕比率(screenRatio),底部安全距离(safeAreaInsetsBtm),区分平台(platform)
let { screenRatio, safeAreaInsetsBtm, platform } = this.$store.state;
// 由于iOS端会自动定位top到状态栏底下,故这里需要判断手机系统
// 因调节Android端的top值能看出头部变化,故转换思路调ios端的top值
let top = 0;
if (platform === 'ios') {
let info = uni.getSystemInfoSync();
top = -info.statusBarHeight;
}
let bottom = safeAreaInsetsBtm / screenRatio;
this.currentWebview.children()[0].setStyle({ top, bottom: parseInt(bottom) });
}
}
复制代码
统一两端的top
至手机屏幕最顶部,bottom
至手机屏幕安全距离之上。
内嵌应用中设置:
// 内嵌应用中,配置一个全局变量管理顶部样式,比如可在vuex的action中写个setPage方法
const actions = {
setPage(context) {
let statusbarHeight = plus.navigator.getStatusbarHeight();
let pageStyle = { paddingTop: `calc(0.2rem + ${statusbarHeight}px)` };
context.commit('pageStyle', pageStyle);
}
};
复制代码
内嵌应用中的数据传递:由 APP 中调用evalJS
方法,调用到的是内嵌应用index.html
中定义的方法,怎么将 APP 传递过来的信息传递到内嵌应用的vue
页面去呢?这里推荐一个插件mitt。
APP 中调用:
export default {
onReady() {
this.currentWebview = this.$mp.page.$getAppWebview();
this.currentWebview.children()[0].evalJS("showTip('提示测试')");
}
}
复制代码
内嵌应用的 index.html 中,引用下载到本地的mitt.umd.js
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<!-- uni 的 SDK,必须引用。 -->
<script src="<%= BASE_URL %>libs/uni-app/uni.app.webview.1.5.2.js"></script>
<!--html页面与vue页面通讯js-->
<script src="<%= BASE_URL %>libs/mitt/mitt.umd.js"></script>
<title>xxx</title>
</head>
<body>
<noscript>
<strong>当前浏览器版本太低,请升级浏览器或使用其他浏览器</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script>
window.bus = window.mitt();
const showTip = (tip) => {
window.bus.emit('showTip', tip);
};
</script>
</body>
</html>
复制代码
内嵌应用 vue 页面中:
export default {
activated() {
// 挂载
window.bus.on('showTip', (tip) => {
alert(tip);
});
}
deactivated() {
// 离开时记得销毁
window.bus.off('showTip');
}
}
复制代码
四、拓展阅读
评论