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

117 阅读4分钟

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

6182de35-4a7c-4609-b06b-94769f9167b3.png 所以增加一下设置 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>