普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月27日首页

Vue3与iframe通信方案详解:本地与跨域场景

作者 小杨梅君
2025年12月27日 11:42

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:是不压缩版的)

<!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 | NanDingGDS</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的web包加载逻辑开始 */
const canvas = document.getElementById("unity-canvas");
const body = document.getElementById("unity3d-body");
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;
}

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) => {}).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) {
console.log("收到来自父窗口的消息:", event.data);
if (event.data.type === "sendJSON2Unity") {
window.SendJSON2Unity(event.data.data);
}
}
});
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 () {
console.log("退出Unity3D");
sendMessageToVue({
type: "quitUnity",
message: "退出Unity3D",
});
};
// window.js2Unity = function (str) {
// // 第一个参数是unity中物体的名称,第二是要调用的方法名称,第三个参数是unity中接收到的参数
// // myGameInstance.SendMessage('Main Camera', 'TestRotation', '')
//     console.log(str);
// }
</script>
</body>
</html>


❌
❌