uniapp H5 ネスト通信ソリューション - webview&iframe
バックグラウンド
uniapp を使用してアプリ プロジェクトを作成する場合、マップのレンダリング、大量の dom ノード、または echart、およびアプリに適さないいくつかの依存関係など、H5 ページを埋め込むために使用する必要がある多くのシナリオがよくありますが、多くの機能 H5ページオンラインでWord文書を閲覧したり、外部プログラムを開いたりするなど、位置情報を取得するなど、独立して行うことはできません.現時点では、H5を使用して外部アプリとの通信を確立する必要があります.以下に具体的なものを提供します. 2 つの通信ソリューションのメソッド
-
ウェブビュー
-
iframe
webview スキーム
WebView ソリューションが画面全体を占有するため、アプリの上部マージンが消え、全体が非常に見苦しくなります. たとえば、Apple ノッチ画面がある場合、ページの一部が埋め込まれます.あなたのアプリでは、上部でブロックされます。そのため、dom を作成した後に webview の上余白と高さを設定し、uniapp の条件付きコンパイル ステートメントを使用して、app-plus の状況であるかどうかを判断する必要があります. 次のコードを直接追加します
。
父親
モバイルアプリのデバッグ
親アプリ内のコード
<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, // 计划创建的 webview
webviewId: 'web-view', // 记录webview的id
}
},
onLoad () {
let height = 0; //定义动态的高度变量
let statusbar = 0; // 动态状态栏高度
uni.getSystemInfo({
// 获取当前设备的具体信息
success: (sysinfo) => {
statusbar = sysinfo.statusBarHeight;
height = sysinfo.windowHeight;
}
});
// #ifdef APP-PLUS
let currentWebview = this.$scope.$getAppWebview(); //获取当前web-view
const that = this
setTimeout(function () {
that.wv = currentWebview.children()[0];
that.wv.setStyle({
//设置web-view距离顶部的距离以及自己的高度,单位为px
top: 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 H5
data = dataRes.detail.data[0];
try {
if (deviceEnvIsH5) {
data = dataRes;
}
} catch (e) {
console.log(e);
}
// #endif
switch (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 H5
this.wv?.contentWindow?.postMessage({
action: action, data: data }, '*');
// #endif
// #ifndef H5
if (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>
親アプリのプロセス全体は次のとおりです:
メッセージの受信: webview dom を初期化して取得し、それを wv 変数に保存します –> webview は @message イベントをトリガーします –> handleMessage –> detailMessageメソッドで、さまざまな状況に変更を加えます。私たちのカスタムアクション識別の異なる扱い
イベントの送信: postMessageを呼び出します–> h5 かどうかを判断します –> 非 h5 (つまりアプリの状況) this.wv.evalJSを呼び出してイベントを送信します
H5の状況
上記では、h5 が存在することについても言及しました。これは、アプリが携帯電話でデバッグするだけでなく、データ処理を改善するためにコンピューターの h5 ページでデバッグするか、独自のネストされた外部レイヤーである埋め込み H5 であるためです。というページなので、H5の場合を扱っています:
見分けやすいように、h5の場合を扱うコードをmountedに入れ、条件付きコンパイルでH5の場合かどうかを判断する必要があります
mounted() {
const deviceEnvIsH5 = getApp().globalData.deviceEnvIsH5;
// #ifdef H5
this.initH5Message();
// #endif
// #ifndef H5
if (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がメッセージをリッスン
子 (ネストされたページ)
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 ファイルをインポートする必要があることに注意してください. 以下のリンクをクリックしてダウンロードできます. 公式のものには
問題があります. 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
}
})
メッセージを受信するには、アプリの初期化時にウィンドウ オブジェクトで receive message メソッドをハングアップし、親アプリを使用して window グローバル メソッドを呼び出し、モバイル アプリで使用するときにメッセージを送信する必要があります。
モバイルアプリのデバッグ
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)
break
default:
console.log("default Message:", {
event })
}
}
実装手順:
親アプリにメッセージを送信: webviewプラグインをmain.ts の前に追加--> webUni.postMessageメソッドでイベントを送信
メッセージの受信: ウィンドウを初期化してカスタム メソッドをマウントします (親呼び出しメソッドの名前と一致する必要があります) --> メソッドが呼び出され、パラメーターを渡すことによってメッセージが受信されます
親ノードはH5ケース
メッセージの送信は上記のアプリと同じです。
メッセージの受信: (メッセージを受信するには、アプリで UniAppJSBridgeReady メソッドを監視する必要があり、読み込みが完了していることに注意してください。そうしないと、webUni を使用して 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
}
})
})
}
親アプリにメッセージを送信: main.tsの前にwebviewプラグインを導入--> document.addEventListenerがUniAppJSBridgeReadyの読み込みをリッスン--> webUni.postMessageメソッドがイベントを送信
メッセージの受信: window.addEventListener はメッセージをリッスンします
完全なコード:
父親
<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, // 计划创建的 webview
webviewId: 'web-view',
}
},
mounted() {
const deviceEnvIsH5 = getApp().globalData.deviceEnvIsH5;
// #ifdef H5
this.initH5Message();
// #endif
// #ifndef H5
if (deviceEnvIsH5) {
this.initH5Message();
}
// #endif
},
onLoad() {
let height = 0; //定义动态的高度变量
let statusbar = 0; // 动态状态栏高度
uni.getSystemInfo({
// 获取当前设备的具体信息
success: (sysinfo) => {
statusbar = sysinfo.statusBarHeight;
height = sysinfo.windowHeight;
}
});
// #ifdef APP-PLUS
let currentWebview = this.$scope.$getAppWebview(); //获取当前web-view
const that = this
setTimeout(function () {
that.wv = currentWebview.children()[0];
that.wv.setStyle({
//设置web-view距离顶部的距离以及自己的高度,单位为px
top: 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 H5
console.log('h5');
data = dataRes.detail.data[0];
try {
if (deviceEnvIsH5) {
data = dataRes;
}
} catch (e) {
console.log(e);
}
// #endif
switch (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 H5
this.wv?.contentWindow?.postMessage({
action: action, data: data }, '*');
// #endif
// #ifndef H5
if (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)
break
default:
console.log("default Message:", {
event })
}
}
iframe スキーム
iframe ソリューションを使用すると、埋め込み Web サイトが画面全体を占有する問題を効果的に回避できますが、このソリューションでは H5 ページなどでデバッグしたい場合、クロスドメインの問題、およびサブページがレポートされるという欠点もあります。直接表示することはできませんが、他のページがすべて配置されている場合クロスドメインが有効になっている場合、使用方法は引き続き app-h5 ソリューションと一致しています
携帯電話のデバッグ(アプリプラス)
window メソッドと document メソッドを直接使用して携帯電話でメッセージをやり取りおよび監視することはできないため、公式のrender.jsメソッドを使用する必要があります。
お父さん(アプリ)
<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:
...
break
case 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>
プロセス全体は次のとおりです:
サブページ送信イベントを受信:ページ内のwindow オブジェクトにreceiveRenderData をマウントします--> render.js をロードします --> マウントされた iframe ノードを取得し、それを dom 変数に保存します --> window.addEventListenerはリッスンしますメッセージ -- > this.$ownerInstance.callMethodを介してページ内の receiveRenderData メソッドにメッセージを渡します --> メッセージを処理します
イベントの送信: ビュー ノードを作成し、応答変数messageをバインドします --> ノードのカスタム プロパティ変数の変更イベントを render.js の messageChange メソッドとしてバインドします --> renderjs にマウントして iframe ノードを取得し、それをdom 変数 -- > 父親がメッセージ変数を変更 --> messageChange メソッドを呼び出す --> this.dom.contentWindow.postMessage(param,'*') を通じてメッセージを送信
小人(H5)
H5 ページはウィンドウを直接操作できるため、window.parent.postMessage メソッドを直接呼び出して変数を親アプリに送信できます。
// 给父亲发送消息
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 はメッセージをリッスンします
親ページもH5
父H5
イベントを送信:
const msg = {
data: '12'
}
dom.contentWindow.postMessage(msg, '*')
イベントを受け取る:
window.addEventListener('message', (event) => {
console.log(event, '---msg')
})
子H5
イベントを送信:
const msg = {
data: '12'
}
window.parent.postMessage(msg, '*');
イベントを受け取る:
window.addEventListener('message', (event) => {
console.log(event, '---msg')
})
要約する
ウェブビュー
アドバンテージ:
- 優れた適応性、すべてのデバイスにやさしい
- アプリの便利な H5 デバッグ
欠点:
- ロジックを書くのはより面倒です
- 外部の js パッケージをインポートする必要があります
- 画面全体を占めるため、余白を手動で設定する必要があります
iframe
アドバンテージ:
- 使いやすく、ロジックを書くのが簡単
- 親DOMのサイズに適応可能
欠点:
- ファーザー H5 ローカル デバッグは友好的ではなく、クロスドメインの問題を報告します
- デバイスとの互換性があまりない