ps:本项目使用的vue3技术栈
Vue3与iframe通信方案详解:本地与跨域场景
本文详细介绍了在Vue3项目中,与内嵌iframe(包括本地HTML文件和服务端跨域HTML)进行双向通信的完整解决方案。核心通信方式为postMessage API,并针对不同场景提供了安全可靠的代码示例。
1. iframe加载本地HTML文件
1.1 Vue端通信代码
<template>
...
<iframe
ref="iframe"
name="iframe-html"
src="./index.html"
width="100%"
height="100%"
frameborder="0"
></iframe>
...
</template
如何在vue端跟iframe端加载的.html文件进行通讯呢,看下面的代码
// vue端
...
const sendMsg2iframe = (msg) => {
window["iframe-html"].sendMsg2iframe(msg);
}
...
// index.html
...
window.sendMsg2iframe = function (msg) {
// 接收到vue端发来的消息
}
...
1.2 iframe端(index.html)通信代码
// index.html
function sendMessageToVue(messageData) {
// 发送消息到父窗口
window.parent.postMessage(messageData, window.location.origin);
}
// vue端
// 组件挂载时开始监听消息
onMounted(() => {
window.addEventListener('message', handleReceiveMessage);
});
// 组件卸载时移除监听,防止内存泄漏
onUnmounted(() => {
window.removeEventListener('message', handleReceiveMessage);
});
// 接收来自iframe消息的处理函数
const handleReceiveMessage = (event) => {
// 重要:在实际应用中,应验证event.origin以确保安全
// if (event.origin !== '期望的源') return;
console.log('Vue组件收到来自iframe的消息:', event.data);
// 在这里处理接收到的数据
};
2. iframe加载服务器HTML(跨域场景)
其实还是通过window的postMessage进行通讯,只不过是涉及到了跨域问题,下面是具体的代码,关键在于postMessage的第二个参数上
2.1 html端通信代码
// .html
...
// 获取url并解析出父窗口的origin
const urlParams = new URLSearchParams(window.location.search);
const parentOrigin = urlParams.get('parentOrigin') || window.location.origin;
// 监听来自父窗口的消息
window.addEventListener('message', function (event) {
if (event.origin === parentOrigin) {
console.log('收到来自父窗口的消息:', event.data);
if(event.data.type === 'sendJSON2Unity'){
window.SendJSON2Unity(event.data.data);
}
}
});
function sendMessageToVue(messageData) {
// 发送消息到父窗口
window.parent.postMessage(messageData, parentOrigin);
}
...
2.2 Vue端通信代码
// .vue
...
<iframe
ref="iframeRef"
name="unity-home"
:src="violationDocumentURL"
width="100%"
height="100%"
frameborder="0"
@load="onIframeLoad">
</iframe>
...
// 这里把自己的origin通过URL参数传给iframe
const violationDocumentURL = import.meta.env.VITE_U3D_SERVICE + "具体路径" + "?parentOrigin=" + encodeURIComponent(window.location.origin);
const iframeRef = ref(null);
const iframeOrigin = ref(import.meta.env.VITE_U3D_SERVICE.replace(/\/$/, "")); // iframe加载的资源的origin
const sendToUnity = (data) => {
iframeRef.value.contentWindow.postMessage(
data,
iframeOrigin.value
);
};
// 组件挂载时开始监听消息
onMounted(() => {
window.addEventListener('message', handleReceiveMessage);
});
// 组件卸载时移除监听,防止内存泄漏
onUnmounted(() => {
window.removeEventListener('message', handleReceiveMessage);
});
// 接收来自iframe的消息
const handleMessageFromIframe = (event) => {
// 确保消息来自可信的来源
if (event.origin === iframeOrigin.value) {
if (event.data) {
// do something
}
}
};
ok基本就是这样的
3 服务器HTML端(Unity WebGL示例)
因为我们是加载的unity的webgl包,所以最后附赠一下打出的webgl包的index.html的代码(ps:是不压缩版的)
更新一下如果说出了下面这个错是因为设置的屏幕缩放的问题,大致原因是如果你的屏幕缩放大于100%那么浏览器会认为你在一个高DPI屏幕上,浏览器会设置 window.devicePixelRatio(例如 1.25、1.5、2...)
在 Unity WebGL 中:
Screen.width并不总是等于canvas.width- 它可能等于:
canvas.clientWidth * window.devicePixelRatio - 即使你在 JS 里设
canvas.width = 1920,如果 DPR=1.5,Screen.width可能返回2880
所以增加一下设置
devicePixelRatio 的代码,我这里是直接强制设置为1,确保任何时候都能加载出来,但是缺点是图像可能在高DPI屏幕上轻微模糊
<!DOCTYPE html>
<html lang="en-us" style="width: 100%; height: 100%">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Unity WebGL Player</title>
</head>
<body
id="unity3d-body"
style="text-align: center; padding: 0; border: 0; margin: 0; width: 100%; height: 100%; overflow: hidden">
<canvas id="unity-canvas" style="background: #231f20"></canvas>
<script>
// 👇 在 Unity 加载前就锁定 DPR=1
(function () {
if (typeof window.devicePixelRatio !== "undefined") {
try {
Object.defineProperty(window, "devicePixelRatio", {
value: 1,
writable: false,
configurable: false,
enumerable: true,
});
} catch (e) {
// 某些浏览器可能不允许 defineProperty,降级处理
window.devicePixelRatio = 1;
}
}
})();
</script>
<script>
/** unity的web包加载逻辑开始 */
const canvas = document.getElementById("unity-canvas");
const body = document.getElementById("unity3d-body");
// 设置canvas大小的函数
function resizeCanvas() {
const { clientHeight, clientWidth } = body;
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
var meta = document.createElement("meta");
meta.name = "viewport";
meta.content =
"width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes";
document.getElementsByTagName("head")[0].appendChild(meta);
container.className = "unity-mobile";
canvas.className = "unity-mobile";
} else {
canvas.width = clientWidth;
canvas.height = clientHeight;
// 通知Unity重新渲染
if (myGameInstance) {
myGameInstance.SetFullscreen();
}
}
}
// 初始设置
resizeCanvas();
// 监听窗口大小变化
window.addEventListener('resize', resizeCanvas);
const baseUrl = "Build/webgl";
var loaderUrl = baseUrl + ".loader.js";
var myGameInstance = null;
var script = document.createElement("script");
script.src = loaderUrl;
var config = {
dataUrl: baseUrl + ".data",
frameworkUrl: baseUrl + ".framework.js",
codeUrl: baseUrl + ".wasm",
streamingAssetsUrl: "StreamingAssets",
companyName: "DefaultCompany",
productName: "FanWeiZhang",
productVersion: "0.1.0",
};
script.onload = () => {
createUnityInstance(canvas, config, (progress) => {
sendMessageToVue({
type: "progress",
message: progress,
});
}).then((unityInstance) => {
myGameInstance = unityInstance;
sendMessageToVue({
type: "unityLoaded",
message: "Unity3D加载完成",
});
});
};
document.body.appendChild(script);
/** unity的web包加载逻辑结束 */
// 获取url并解析出父窗口的origin
const urlParams = new URLSearchParams(window.location.search);
const parentOrigin = urlParams.get("parentOrigin") || window.location.origin;
// 监听来自父窗口的消息
window.addEventListener("message", function (event) {
if (event.origin === parentOrigin) {
if (event.data.type === "sendJSON2Unity") {
window.SendJSON2Unity(event.data.data);
}
else if (event.data.type === "loadAssetsAddress") {
loadAssetsAddress = event.data.message;
}
}
});
function sendMessageToVue(messageData) {
// 发送消息到父窗口
window.parent.postMessage(messageData, parentOrigin);
}
window.SendJSON2Unity = function (str) {
console.log("发送到Unity的JSON字符串:", str);
myGameInstance.SendMessage("WebController", "receiveJSONByWeb", str);
};
window.QuiteUnity = function () {
sendMessageToVue({
type: "quitUnity",
message: "退出Unity3D",
});
};
// window.js2Unity = function (str) {
// // 第一个参数是unity中物体的名称,第二是要调用的方法名称,第三个参数是unity中接收到的参数
// // myGameInstance.SendMessage('Main Camera', 'TestRotation', '')
// console.log(str);
// }
</script>
</body>
</html>