跨域訪問-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