uniapp H5嵌套通讯方案-webviewiframe
uniapp H5嵌套通讯方案-webview&iframe
- 背景
- webview方案
- 父级
- 手机app调试情况
- H5情况
- 子级(嵌套页面)
- 手机app调试情况
- 父节点是H5情况
- 完整代码:
- 父亲
- 孩子
- iframe 方案
- 手机调试情况(app-plus)
- 父亲(app)
- 孩子(H5)
- 总结
背景
在我们使用uniapp制作app的项目的时候往往会有很多的场景需要使用到嵌入H5页面,比如需要渲染地图,会大量操作dom节点,或者echarts,以及一些依赖不适配app,但是很多功能H5页面是无法独立完成的,比如获取定位,比如在线浏览word文档,或者打开外部程序等,这时候我们就需要使用H5与外部的app建立通讯以下我将提供两个通讯方案的具体方法
webview
iframe
webview方案
值得注意的是webview方案会导致占满整个屏幕,这样app内的上边距就会消失,整个会很不好看,比如你是苹果刘海屏幕,那你的app嵌入的页面就会在顶部有一部分被遮挡。所以我们需要创建dom后设置webview的上边距以及高度,利用uniapp的条件编译语句判断是否是app-plus情况
以下直接上代码:
父级
手机app调试情况
父级app内的代码
<template><view class="content"><web-view ref="webViewDom" :id="webviewId" src="http://..." @message="handleMessage"></web-view></view>
</template>
<script>
// 我定义的通讯标识符字典,方便通讯的全局管理
import { WEB_VIEW_EVENT } from '@/api/AppEvent';
export default {data () {return {wv: null, // 计划创建的 webviewwebviewId: 'web-view', // 记录webview的id}},onLoad () {let height = 0; //定义动态的高度变量let statusbar = 0; // 动态状态栏高度uni.getSystemInfo({ // 获取当前设备的具体信息success: (sysinfo) => {statusbar = sysinfo.statusBarHeight;height = sysinfo.windowHeight;}});// #ifdef APP-PLUSlet currentWebview = this.$scope.$getAppWebview(); //获取当前web-viewconst that = thissetTimeout(function () {that.wv = currentWebview.children()[0];that.wv.setStyle({ //设置web-view距离顶部的距离以及自己的高度,单位为pxtop: statusbar,height: height - statusbar,})}, 500);// #endif// 如果想向webview发送事件就调用postMessage方法},methods: {// webview向外部发送消息--app 接收到的消息handleMessage (event) {console.log('接收到的消息:' + JSON.stringify(event.detail.data));this.detailMessage(event) // 处理信息},/*** 处理 webview 向客户端传递 事件*/detailMessage (dataRes) {let data = JSON.parse(JSON.stringify(dataRes));const deviceEnvIsH5 = getApp().globalData.deviceEnvIsH5;// #ifndef H5data = dataRes.detail.data[0];try {if (deviceEnvIsH5) {data = dataRes;}} catch (e) {console.log(e);}// #endifswitch (data.action) {case WEB_VIEW_EVENT.WEBVIEW_SUCCESS:console.log('webView加载成功', data);break;case WEB_VIEW_EVENT.MAP_SUCCESS:console.log('地图MAP_SUCCESS', data);...break;default: break; }},/*** 向webview 发送消息* @param action* @param data*/postMessage (action, data) {const deviceEnvIsH5 = getApp().globalData.deviceEnvIsH5;// #ifdef H5this.wv?.contentWindow?.postMessage({ action: action, data: data }, '*');// #endif// #ifndef H5if (deviceEnvIsH5) {// 宿主机在非h5中,但其实是嵌套的webview 还是走传统 webview 通信this.wv?.contentWindow.postMessage({ action: action, data: data }, '*');} else {switch (action) {case WEB_VIEW_EVENT.SEND_LOCATION: // 发送坐标const str = JSON.stringify({ action: action, data: data });this.wv.evalJS(`window.getFatherMessage(${str})`);break;default: break; }}// #endif},}}
</script>
父级app整个流程是:
接收消息:初始化获取webview dom 保存在wv变量内–>webview触发@message事件–>handleMessage -->在detailMessage方法内我们通过对我们自定义的action标识对不同情况做出不同的处理
发送事件:调用postMessage–>判断是否h5–>非h5(也就是app情况)调用this.wv.evalJS发送事件
H5情况
以上也提到了会有h5的情况,那是因为我们app不只是会在手机上调试,也会在我们的电脑h5页面上调试也是为了更好的对数据处理,或者我们本身嵌套的外层就是一个嵌入的H5页面,所以我们针对H5的情况进行处理:
为了方便我们区分我把处理h5情况的代码放入mounted内,同样也需要用到条件编译判断是否是H5的情况
mounted() {const deviceEnvIsH5 = getApp().globalData.deviceEnvIsH5;// #ifdef H5this.initH5Message();// #endif// #ifndef H5if (deviceEnvIsH5) {this.initH5Message();}// #endif},methods: {/*** 初始化h5下的消息机制*/initH5Message() {const currentWebview = document.querySelectorAll('#' + this.webviewId);for (let i = 0; i < currentWebview.length; i++) {// h5 下获取iframe 元素if (currentWebview[i].nodeName === 'IFRAME' && currentWebview[i].id === this.webviewId) {that.wv = currentWebview[i];}}const that = this;window.addEventListener('message', event => {if (event.data?.data?.arg) {this.detailMessage(event.data?.data?.arg);}});},}
在h5下接收消息就会变得很简单直接用addEventListener就可以监听到,发送消息还是调用我们之前的postMessage方法即可,不同的是获取webview节点的方式,我们可以直接用id获取消息接收
发送流程是:获取webview实例保存在wv变量中 --> 判断是否H5 --> 是H5 this.wv?.contentWindow?.postMessage发送消息
接收消息:初始化获取webview实例保存在wv变量内 -->判断是否H5 --> 是H5 window.addEventListener监听message
子级(嵌套页面)
在index.html 页面内引入我们的webview文件
<script type="text/javascript" src="./src/static/WebView/uni.webview.js"></script>
<script type="module" src="/src/main.ts"></script>
注意要在main.ts之前引入
这边的webview文件可以点击我下方的链接下载,官方的会有问题,因为官方的暴露名称也是叫uni这和uniapp自带的全局对象重复名称了,所以会导致无法无法调用webview.js的api的问题,如果下载了官方的需要自己手动去将webview.js文件内的uni名称改成其他的,我这边改成了webUni
官网下载链接:
https://gitee.com/dcloud/uni-app/raw/dev/dist/uni.webview.1.5.4.js
我的链接:
https://github.com/572031690/vue3-uniapp-template/blob/main/src/static/WebView/uni.webview.js
发送消息
webUni.postMessage({data: {action: WEB_VIEW_EVENT.MAP_SUCCESS, // 地图初始化完毕data: true}})
接收消息需要在app初始化的时候将我们的接收消息方法挂在到window对象上,用与我们在手机app使用的时候父级app调用我们window全局上的方法来给我们发送消息
手机app调试情况
app.vue内:
window.getFatherMessage = getFatherMessage
/*** @name 消息机制 App 版本* @param str 消息数据*/
const getFatherMessage = (str: { action: string, data: any }) => {detailMessage(str)
}
/*** @name 处理消息机制*/
const detailMessage = (event: { action: string, data: any }) => {console.log(event, 'event')switch (event.action) {case WEB_VIEW_EVENT.SEND_LOCATION:systemStore.setMapCenter(event.data)breakdefault:console.log("default Message:", { event })}
}
实现步骤:
发送消息给父亲app:在main.ts前加入webview插件 -->webUni.postMessage方法发送事件
接收消息:初始化window挂载自定义方法(需要与父级调用方法名称一致) -->该方法被调用并通过传参接收到消息
父节点是H5情况
发送消息还是和上面app情况一样
接收消息:(需要注意的是接收消息我们需要在app内监听UniAppJSBridgeReady方法以及加载完毕,不然使用webUni发送事件会报错)
app.vue
onLaunch(function () {useLoadUniAppScript()
})
/*** 消息机制*/
function useLoadUniAppScript() {document.addEventListener('UniAppJSBridgeReady', function () {// 接受子页面发来的信息window.addEventListener('message', event => {if (event.data) {detailMessage(event.data)}})webUni.postMessage({data: {action: WEB_VIEW_EVENT.WEBVIEW_SUCCESS, // webview加载成功data: true}})})
}
发送消息给父亲app:在main.ts前引入webview插件 --> document.addEventListener监听UniAppJSBridgeReady加载完毕–>webUni.postMessage方法发送事件
接收消息:window.addEventListener监听message
完整代码:
父亲
<template><view class="content"><web-view ref="webViewDom" id="web-view" src="http://..." @message="handleMessage"></web-view></view>
</template><script>
import { WEB_VIEW_EVENT } from '@/api/AppEvent';export default {data () {return {wv: null, // 计划创建的 webviewwebviewId: 'web-view', }},mounted() {const deviceEnvIsH5 = getApp().globalData.deviceEnvIsH5;// #ifdef H5this.initH5Message();// #endif// #ifndef H5if (deviceEnvIsH5) {this.initH5Message();}// #endif},onLoad() {let height = 0; //定义动态的高度变量let statusbar = 0; // 动态状态栏高度uni.getSystemInfo({ // 获取当前设备的具体信息success: (sysinfo) => {statusbar = sysinfo.statusBarHeight;height = sysinfo.windowHeight;}});// #ifdef APP-PLUSlet currentWebview = this.$scope.$getAppWebview(); //获取当前web-viewconst that = thissetTimeout(function () {that.wv = currentWebview.children()[0];that.wv.setStyle({ //设置web-view距离顶部的距离以及自己的高度,单位为pxtop: statusbar,height: height - statusbar,})}, 1000);// #endif},methods: {// webview向外部发送消息handleMessage(event) {console.log('接收到的消息:' + JSON.stringify(event.detail.data));this.detailMessage(event)},/*** 处理 webview 向客户端传递 事件*/detailMessage(dataRes) {let data = JSON.parse(JSON.stringify(dataRes));const deviceEnvIsH5 = getApp().globalData.deviceEnvIsH5;// #ifndef H5console.log('h5');data = dataRes.detail.data[0];try {if (deviceEnvIsH5) {data = dataRes;}} catch (e) {console.log(e);}// #endifswitch (data.action) {case WEB_VIEW_EVENT.WEBVIEW_SUCCESS:console.log('webView加载成功', data);break;case WEB_VIEW_EVENT.MAP_SUCCESS:console.log('地图MAP_SUCCESS', data);...break;default:break;}},/*** 向webview 发送消息* @param action* @param data*/postMessage (action, data) {const deviceEnvIsH5 = getApp().globalData.deviceEnvIsH5;// #ifdef H5this.wv?.contentWindow?.postMessage({ action: action, data: data }, '*');// #endif// #ifndef H5if (deviceEnvIsH5) {// 宿主机在非h5中,但其实是嵌套的webview 还是走传统 webview 通信this.wv?.contentWindow.postMessage({ action: action, data: data }, '*');} else {switch (action) {case WEB_VIEW_EVENT.SEND_LOCATION: // 发送坐标const str = JSON.stringify({ action: action, data: data });this.wv.evalJS(`window.getFatherMessage(${str})`);break;}}// #endif},/*** 初始化h5下的消息机制*/initH5Message() {const currentWebview = document.querySelectorAll('#' + this.webviewId);for (let i = 0; i < currentWebview.length; i++) {// h5 下获取iframe 元素if (currentWebview[i].nodeName === 'IFRAME' && currentWebview[i].id === this.webviewId) {that.wv = currentWebview[i];}}const that = this;window.addEventListener('message', event => {if (event.data?.data?.arg) {this.detailMessage(event.data?.data?.arg);}});},}
}
</script>
孩子
index.html
<script type="text/javascript" src="./src/static/WebView/uni.webview.js"></script> // 在main之上
<script type="module" src="/src/main.ts"></script>
app.vue
onLaunch(function () {useLoadUniAppScript()
})/*** 消息机制*/
function useLoadUniAppScript() {document.addEventListener('UniAppJSBridgeReady', function () {// 接受子页面发来的信息window.addEventListener('message', event => {if (event.data) {detailMessage(event.data)}})webUni.postMessage({data: {action: WEB_VIEW_EVENT.WEBVIEW_SUCCESS, // webview加载成功data: true}})})
}/*** @name 消息机制 App 版本* @param str 消息数据*/
const getFatherMessage = (str: { action: string, data: any }) => {detailMessage(str)
}
window.getFatherMessage = getFatherMessage
/*** @name 处理消息机制*/
const detailMessage = (event: { action: string, data: any }) => {console.log(event, 'event')switch (event.action) {case WEB_VIEW_EVENT.SEND_LOCATION:systemStore.setMapCenter(event.data)breakdefault:console.log("default Message:", { event })}
}
iframe 方案
使用iframe方案可以有效的避免嵌入网站占满整个屏幕的问题,但是也有弊端就是如果此方案想在H5页面下调试等等会报跨域的问题,直接无法显示子页面,不过对方页面如果全部放开跨域的话,使用方法还是和app-h5方案一致的
手机调试情况(app-plus)
由于我们手机上无法直接使用window,document方法来通讯以及监听message,所以我们需要借助官方的render.js方法
父亲(app)
<template lang="pug"><view style="height:100vh !important;"><iframe id="iframeId" style="margin-top: 40rpx; width:100%;height: calc(100% - 40rpx); " ref="iframeDom" src="..."></iframe><view :message="message" :change:dataItem="renderModal.messageChange"></view></view>
</template>
<script>
import { WEB_VIEW_EVENT } from '../../static/AppEvent'
export default {data() {return {message: ''}},mounted() {window.receiveRenderData = this.receiveRenderData},methods: {receiveRenderData(e) {//接收的值console.log(e, '-father-event')switch (e.action) {case WEB_VIEW_EVENT.REQUEST_LOCATION:...breakcase WEB_VIEW_EVENT.OPEN_FILE:...default:break}},// 发送事件snedMessage(action, data) {this.message = {action,data}},}
}
</script>
<script module="renderModal" lang="renderjs">export default {data() {return {dom: '',}},mounted() {this.dom = document.getElementById('iframeId')// 接收iframe传过来的值window.addEventListener('message', (e)=> {this.emitData(e.data) });},methods: {emitData(e) {// 将值传到当前页面this.$ownerInstance.callMethod('receiveRenderData',e)},// data的值发生改变时会触发dataChange并且将值传到iframe页面中messageChange(e) {const param = {data:e}this.dom.contentWindow.postMessage(param,'*')}}}
</script>
整个流程是:
接收子页面发送事件:页面内将receiveRenderData挂载到window对象上 --> 加载render.js --> mounted内获取iframe节点保存在dom变量内 --> window.addEventListener监听message --> 将讯息通过this.$ownerInstance.callMethod传给页面内的receiveRenderData方法 --> 处理讯息
发送事件:创建一个view节点绑定响应式变量message --> 节点上绑定自定义属性变量change事件为render.js内的messageChange方法 --> renderjs内mounted获取iframe节点保存在dom变量内 --> 父亲改变message变量 --> 调用messageChange方法 --> 通过this.dom.contentWindow.postMessage(param,‘*’)发送讯息
孩子(H5)
H5页面可以直接操作window,所以我们可以直接调用window.parent.postMessage方法将变量发送给父亲app
// 给父亲发送消息
window.parent.postMessage({action: 'REQUEST_LOCATION', // 请求获取定位data: true},'*'
);// 接收消息:
onLaunch() {this.getInfo()
},
methods: {getInfo() {const that = this//接受父页面发来的消息window.addEventListener('message', event => {// 根据上面制定的结构来解析iframe内部发回来的数据const data = event.data;that.detailMessage(data)});},
}
发送消息: 调用window.parent.postMessage方法
接收消息:window.addEventListener监听 message
总结
webview
优点:
- 适配性好,对于个个设备使用友好
- 方便app的H5调试
缺点:
- 书写逻辑较为繁琐
- 需要引入外部js包
- 会占满整个屏幕,需要手动设置边距
iframe
优点:
- 使用起来书写逻辑方便
- 可以自适应父级dom大小
缺点:
- 父亲H5本地调试不友好,会报跨域问题
- 对设备兼容性不高
uniapp H5嵌套通讯方案-webviewiframe相关推荐
- 【uniapp】H5扫码方案,解决uni.scanCode不支持H5的问题
1. 背景 uniapp 中自带的 uni.scanCode 只能应用于 非H5,在H5上是没有提供该类扫码功能. 在参考了前辈的文章后(在此特别感谢 uniapp H5扫码实现 提供的方案和 ...
- 水泥厂堆取料机无线通讯方案
2019独角兽企业重金招聘Python工程师标准>>> 水泥厂堆取料机无线通讯方案 --用户实例&详细方案 作者:沈拓 本文是原创文章,图文已申请版权维护 如要进行商业转载请 ...
- 仿微信 java IM即时通讯 | uni-app IM即时通讯 | vue IM即时通讯桌面端 | 源码出售
仿微信 uniapp IM 即时通讯 源码 & 即时通讯知识小科普 源码,不加密,方便二开! 私有云服务,高效稳定,数据安全,无第三方服务 拓展性强,可拓展任意的消息体类型! 性能优秀,资源足 ...
- UNI-APP H5应用白屏(空白页)解决方案
UNI-APP H5应用白屏(空白页)解决方案 写在前面 最近公司有一些临时业务急需上线,考虑到H5应用测试部署的便捷性,因此从技术上采用H5网页的方式快速响应业务的需求,由于自己对uni-app 比 ...
- The Internet Communications Engine (Ice) 跨平台异构通讯方案 第一弹-ICE简介
.net中的通讯方案很多,从.net Remoting,MSMQ,Webservice,WSE,WCF等等,他们都有一个特点,易用,但是都不能跨语种异构,如果你服务端要用java开发,客户端用C#开发 ...
- uniapp h5 微信打开双标题处理
uniapp h5 在微信内打开双标题的问题 解决办法 在page.json文件下将 globalStyle 下的 navigationStyle 设置为 "custom" 即可解 ...
- PHP开发的H5即时通讯聊天系统源码 带群聊 可封装APP
H5即时通讯聊天系统源码,源码可用于聊天.交友.客服等平台,可创建群聊,可添加好友,可发朋友圈,运行稳定,支持封装APP. 源码带有详细文档搭建教程,直接使用宝塔搭建即可,前端需要使用工具运行和编译, ...
- 欧姆龙CP/CJ系列PLC以太网通讯方案
欧姆龙CPCJ系列PLC以太网通讯方案 描述: NET30-CS用于欧姆龙CP1E/CP1H/CP1L/CJ1M/CJ2M等系列PLC. NET30-CS 桥接器采用三通设计,不占用PLC通讯口,不对 ...
- Android 模块 -- 基于XMPP协议的手机多方多端即时通讯方案
目 录 基于XMPP协议的手机多方多端即时通讯方案................................................................. 1 目 录 ...
最新文章
- 未能在全局命名空间中找到类型或命名空间名称“Wuqi”
- 【转】谈谈三层架构中MODEL的作用
- 使用commandfield删除、修改gridview
- MPlayer在ARM上的移植(S5PV210开发板)
- html日期选择框_第十课 日期选择框(datepicker)的操作
- mysql general clomun_几个简单的sql查询
- FAT磁盘分配策略简说
- 读懂hadoop、hbase、hive、spark分布式系统架构
- 推荐Go语言开源项目:Excelize ,获取阿里云ECS实例监控数据导出到自定义Excel表格(二)
- Keil5安装教程(包含C51与MDK共存)WIN10 亲测可用
- 三维全景技术的基本原理,三维全景虚拟现实图片应用
- linux pbs 安装包,【Linux】单计算机安装PBS系统(Torque)与运维
- 中国移动和路由AP218免拆机刷机教程
- 智能人物画像综合分析系统——Day21
- 关于Qt bindValue函数出错问题
- 基于ssm小说阅读安卓APP项目
- 可怕的KCFErrordomainCFNetWork 303
- 长角牛网络监听 arp欺骗
- 滴滴十大技术方向开源项目出炉!有点牛啊!
- esp8266,esp32中的SPI FLASH 访问模式(QIO QOUT DIO DOUT)
热门文章
- ping的各种意义、作用
- 【STC8H8K64U】开天斧3.1学习笔记——LED闪烁
- 计算机屏幕有条线,为什么电脑屏幕上有条线
- 关于显示器,神马ips、pls、tn的
- 机器学习——周志华读书笔记
- 利用Yocto构建嵌入式Linux教程3_1--Yocto构建系统的配置文件
- 深入AXI4总线- [一] 握手机制
- Python爬虫进阶之字体反扒保姆级教程!
- Adobe Acrobat XI Pro闪退问题解决办法
- Java小白 前端后端开发 环境搭建【jdk+idea+webstorm+maven+nodejs+vue+mysql】