跨域访问-iframe之间通信
2017-06-28 00:00
169 查看
在项目中,经常会使用到 iframe,把其它域名的内容嵌入到页面中,这对于我们来说是个很方便的方法,但是,有时候,无可避免需要多个iframe间或者iframe与主页面之间进行通信,比如交换数据或者触发一系列事件。
本文将重点介绍如何在跨域的情况下进行iframe通信。首先,我们了解一下在同域情况下,iframe的通信方法。
(1)main.jsp通过iframe加载sub_1.jsp
(2)加载sub_1.jsp完成后,主页面通过JS读取子页面的标题显示到主页面的 p 标签,另外,调用子页面提供的方法 fun()
(3)子页面读取主页面的标题显示到 span 标签
(4)点击子页面的按钮,调用主页面的 fun() 方法
清单:main.jsp
清单:sub_1.jsp
这时,通过浏览器的控制台后台,可以观察到报错。
例如下面这种跨子域的操作是不允许的:
解决方法:
把主页的域和子页面的域设置为同一个二级域名下,比如a.com,它们之间就可以访问了:
(1)在main.jsp加上js代码
(2)在sub_1.jsp加上js代码
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
清单:子页面sub_3.jsp
这样就实现了上面所述的功能。既然,已经知道了如何使用postMessage,下面将抽取出来得到一个通用的模块post_msg.js
如何使用?
假如main.jsp中有一个方法setTitleVal(arg)是对HTML标签的赋值,sub_3,jsp需要把子页面的标题的值传到父页面setTitleVal方法中,完成赋值。
(1)需要在main.jsp引入post_msg.js,并且暴露etTitleVal方法
(2)sub_3,jsp引入post_msg.js,调用下面代码即可。
本文将重点介绍如何在跨域的情况下进行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 style="font-weight:bold;" id="sub_title">子页面加载完成后,将在此处显示子页面title</p> <iframe width="500" height="300" id="frame"></iframe> <p> <button onclick="loadFrame('sub_1.jsp');">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 onclick="callparent();">调用父页面方法</button> <div> 父页面的标题是:<span id="parent_title"></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 onclick="loadFrame('http://sub.a.com/sub_2.jsp');">load sub page</button>
这时,通过浏览器的控制台后台,可以观察到报错。
Uncaught DOMException: Blocked a frame with origin "http://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 style="font-weight:bold;" id="sub_title">子页面加载完成后,将在此处显示子页面title</p> <iframe width="500" height="300" id="frame"></iframe> <p> <%--跨全域访问--%> <button onclick="loadFrame('http://www.a.com/sub_3.jsp');">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);
相关文章推荐
- 嵌套iframe下父子页面之间的通信(跨域及同域的分析)
- iframe与主框架跨域相互访问方法
- 解决ASP.NET AJAX在frame及iframe中跨域访问的问题
- CP="CAO PSA OUR" 用P3P header解决iframe跨域访问cookie
- iframe跨域访问
- iframe跨域访问
- 通过 iframe 和 location.hash 进行跨域通信
- JavaScript使ifram跨域相互访问及与PHP通信的实例
- iframe跨域通信的通用解决方案
- iframe跨域通信封装详解
- iframe与主框架跨域相互访问方法
- iframe中跨域页面访问parent的方法
- 在IE浏览器中iframe跨域访问cookie/session丢失的解决办法
- 父子页面之间跨域通信的方法
- iframe跨域访问
- iframe跨域访问cookie问题, A站iframe引用其它站(B站)的内容时,B站的页面获取不到B站种下的Cookies。
- 两个林之间的跨域访问
- [转]父子页面之间跨域通信的方法
- 不是同源域名之间的跨域访问
- 利用iframe实现ajax跨域通信的实现原理(图解)