閱讀860 返回首頁    go 阿裏雲 go 技術社區[雲棲]


跨域訪問-iframe之間通信

在項目中,經常會使用到 iframe,把其它域名的內容嵌入到頁麵中,這對於我們來說是個很方便的方法,但是,有時候,無可避免需要多個iframe間或者iframe與主頁麵之間進行通信,比如交換數據或者觸發一係列事件。

本文將重點介紹如何在跨域的情況下進行iframe通信。首先,我們了解一下在同域情況下,iframe的通信方法。

frame同域通信

假設,www.a.com域的main.jsp需要與子頁麵sub_1.jsp的互相訪問,實現的功能如下:

  • (1)main.jsp通過iframe加載sub_1.jsp
  • (2)加載sub_1.jsp完成後,主頁麵通過JS讀取子頁麵的標題顯示到主頁麵的 p 標簽,另外,調用子頁麵提供的方法 fun()
  • (3)子頁麵讀取主頁麵的標題顯示到 span 標簽
  • (4)點擊子頁麵的按鈕,調用主頁麵的 fun() 方法

清單:main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>我是主頁標題</title>
    <script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>

<p  >子頁麵加載完成後,將在此處顯示子頁麵title</p>

<iframe width="500" height="300" ></iframe>

<p>
    <button >load sub page</button>
</p>

<script type="text/javascript">

    function loadFrame(page) {

        var $frame = $("#frame");
        $frame.attr("src", page); //加載頁麵

        $frame.one("load", function () {
            var subWin = document.getElementById("frame").contentWindow; //獲取子窗口的window對象

            $("#sub_title").html(subWin.document.title);

            subWin.fun();
        });
    }

    /**
     * 提供給子頁麵調用的函數
     * @param arg
     */
    function fun(arg) {
        alert("main頁麵的fun方法被frame頁麵調用,參數為: " + arg);
    }

</script>
</body>
</html>

清單:sub_1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>sub_1:這是第一個子頁麵的標題</title>
    <script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
這是第一個子頁麵
<button >調用父頁麵方法</button>

<div>
    父頁麵的標題是:<span ></span>
</div>

<script type="text/javascript">
    /**
     * 調用父頁麵的fun方法
     * @param arg
     */
    function callparent() {
        var pwin = window.parent; //獲取父頁麵的window對象

//        var pwin = window.top; //獲取頂層父頁麵的window對象

        pwin.fun(123456)
    }

    /**
     * 供父頁麵調用
     */
    function fun() {
        console.log("子頁麵的方法fun()被調用了。")
    }

    $(function () {
        //把父頁麵的title設置到p標簽

        var pwin = window.parent; //獲取父頁麵的window對象

        $('#parent_title').html(pwin.document.title)
    });
</script>
</body>
</html>

frame跨子域通信

如果上麵的sub_1.jsp頁麵放在sub.a.com,同樣通過www.a.com的main.jsp的iframe載入sub_1.jsp

 <%--跨資源訪問,--%>
 <button >load sub page</button>

這時,通過瀏覽器的控製台後台,可以觀察到報錯。

Uncaught DOMException: Blocked a frame with origin "https://www.a.com" from accessing a cross-origin frame.

例如下麵這種跨子域的操作是不允許的:

 var pwin = window.parent; //獲取父頁麵的window對象

 $('#parent_title').html(pwin.document.title)

解決方法: 把主頁的域和子頁麵的域設置為同一個二級域名下,比如a.com,它們之間就可以訪問了:

(1)在main.jsp加上js代碼

document.domain = "a.com"; //提升為二級域名

(2)在sub_1.jsp加上js代碼

document.domain = "a.com"; //提升為二級域名

frame跨全域通信

html5中提供了window.postMessage這麼一種用於安全的使用跨源通信的方法,可以實現跨文本檔、多窗口、跨域消息傳遞。

postMessage(data,origin)方法接受兩個參數:

(1)data:要傳遞的數據,html5規範中提到該參數可以是JavaScript的任意基本類型或可複製的對象,然而並不是所有瀏覽器都做到了這點兒,部分瀏覽器隻能處理字符串參數,所以我們在傳遞參數的時候需要使用JSON.stringify()方法對對象參數序列化,在低版本IE中引用json2.js可以實現類似效果。

(2)origin:字符串參數,指明目標窗口的源,協議+主機+端口號[+URL],URL會被忽略,所以可以不寫,這個參數是為了安全考慮,postMessage()方法隻會將message傳遞給指定窗口,當然如果願意也可以建參數設置為"*",這樣可以傳遞給任意窗口,如果要指定和當前窗口同源的話設置為"/"。

接下來,我們完成一個簡單的示例,熟悉原理:子頁麵(www.b.com)發送子頁麵的標題到父頁麵(www.a.com),父頁麵接收參數,賦值到付頁麵的HTML文檔中。

清單:父頁麵main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>我是主頁標題</title>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script>
        window.onmessage = function (evt) {
            evt = event || evt; //兼容性,獲取事件
            console.log(evt.origin); //打印來源
            $("#sub_title").html(evt.data);
    }
    </script>
</head>
<body>

<p  >子頁麵加載完成後,將在此處顯示子頁麵title</p>

<iframe width="500" height="300" ></iframe>

<p>
    <%--跨全域訪問--%>
    <button >load sub page</button>
</p>

<script type="text/javascript">

    function loadFrame(page) {
        var $frame = $("#frame");
        $frame.attr("src", page); //加載頁麵
    }

    function setTitleVal(text) {
        $("#sub_title").html(text);
    }

</script>
</body>
</html>

清單:子頁麵sub_3.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>這是第一個子頁麵的標題</title>
    <script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>

<script type="text/javascript">

    $(function () {
        window.parent.postMessage(document.title,"*"); //window.parent是父頁麵的window對象
    });
</script>
</body>
</html>

這樣就實現了上麵所述的功能。既然,已經知道了如何使用postMessage,下麵將抽取出來得到一個通用的模塊post_msg.js

/**
 * 封裝postMessage方法,使發出的信息能被通用的message listener處理
 * @param {Window} targetWindow 目標框架窗口
 * @param {String} cmd  在目標窗口window空間中存在的方法名,消息發送後,目標窗口執行此名稱的方法
 * @param {Array} args cmd方法需要的參數,多個參數時使用數組
 * @param {Function} callback 可選的參數,如果希望獲得目標窗口的執行結果,使用此參數,結果返回後自動以返回結果為參數調用此回調方法
 */
function sendFrmMsg(targetWindow, cmd, args, callback) {
    var fname;
    if (callback) {
        fname = "uuid" + new Date().getTime(); //生成唯一編碼
        window[fname] = callback;
    }

    args = (args instanceof Array) ? args : [args];

    var msg = {
        cmd: cmd,
        args: args,
        returnCmd: fname
    }


    targetWindow.postMessage(JSON.stringify(msg), "*");
}

/**
 * 獲取另一個跨域窗口上的變量值
 * @param {Window} targetWindow 目標框架窗口
 * @param {String} varName 待獲取值的變量名稱
 * @param {Function} callback 獲取成功後調用此回調方法處理變量值
 */
function getFrmVarValue(targetWindow, varName, callback) {
    sendFrmMsg(targetWindow, "getOtherFrameVarValue", [varName], callback);
}

/**
 * 給另一窗口設置變量值
 * @param {Window} targetWindow 目標框架窗口
 * @param {String} varName 待設置變量名
 * @param {Object} value 待設置變量值
 */
function setFrmVarValue(targetWindow, varName, value) {
    sendFrmMsg(targetWindow, "setOtherFrameVarValue", [varName, value]);
}

/**
 * 獲取窗口變量值
 * @param {String} varName 變量名稱
 */
function getOtherFrameVarValue(varName) {
    try {
        eval("var ret = " + varName);
        return ret;
    } catch (e) {
        console.log(e);
    }
}

/**
 * 設置變量值
 * @param {String} varName 變量名稱
 * @param {Object} value 變量值
 */
function setOtherFrameVarValue(varName, value) {
    try {
        if (typeof value === "string") { // 字符串類型在拚接表達式時需要加引號
            value = "'" + value + "'";
        }
        eval(varName + "=" + value);
    } catch (e) {
        console.log(e);
    }
}

/**
 * message 事件監聽器,自動根據cmd執行
 * @param {Object} evt
 * obj 形式:
 * {
 *     cmd: "目標窗口的function引用名",
 *     args: "參數列表" , 數組形式,
 *     [returnCmd]: "可選的,表示雙向調用的回調function引用名,在回調時"
 *  }
 */
window.onmessage = function (evt) {
    evt = evt || event;

    var source = evt.origin;

    try {
        var obj = JSON.parse(evt.data);
        console.log(obj);
    } catch (e) {
        console.log(e);
    }

    if (obj.cmd) {
        // 拚成:setVal(obj.arg0, obj.arg1);
        var cmd = obj.cmd + "(";

        if (obj.args) { //拚接參數
            for (var i = 0; i < obj.args.length; i++) {
                obj["arg" + i] = obj.args[i];
                if (i > 0) {
                    cmd += ",";
                }
                cmd += "obj.arg" + i;
            }
        }

        cmd += ")";
        // 以上代碼完成後,如obj.cmd="fun",則拚接字符串如下:fun(obj.arg1, obj.arg2);
        // 在通過eval執行時,各參數即obj.arg1等已綁定到obj對象上,所以取的是傳遞過來的參數數組值
        try {
            var ret = eval(cmd);
            if (obj.returnCmd) { //把結果返回給源
                evt.source.postMessage(JSON.stringify({
                    cmd: obj.returnCmd,
                    args: [ret]
                }), evt.origin);
            }
        } catch (e) {
            if (console) console.log(e);
        }
    }
}

如何使用? 假如main.jsp中有一個方法setTitleVal(arg)是對HTML標簽的賦值,sub_3,jsp需要把子頁麵的標題的值傳到父頁麵setTitleVal方法中,完成賦值。
(1)需要在main.jsp引入post_msg.js,並且暴露etTitleVal方法
(2)sub_3,jsp引入post_msg.js,調用下麵代碼即可。

 sendFrmMsg(window.parent, "setTitleVal", document.title);

原文鏈接

最後更新:2017-06-30 15:02:06

  上一篇:go  996月入三萬,他卻要放棄
  下一篇:go  商洛網站建設:提高關鍵詞排名讓SEO效果翻倍