postMessage iframe跨域解决高度自适应

今天学习一个新的window方法:window.postMessagewindow.postMessage() 方法可以安全地实现跨源通信。postMessage是html5引入的API,postMessage()方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案.

之前的一篇iframe高度自适应的文章想必很多人看过,在同源中,处理比较方便,但是对于不同源的iframe处理起来就比较麻烦,需要用到代理页面。而postMessage则直接两个页面交流。

属性详解

postMessage

otherWindow.postMessage(message, targetOrigin, [transfer])

otherWindow

窗口的一个引用,比如iframe的contentWindow属性,执行window.open返回的窗口对象,或者是命名过的或数值索引的window.frames.

message

要发送到其他窗口的数据,它将会被[!结构化克隆算法](https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm)序列化.这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化.

targetOrigin

通过窗口的origin属性来指定哪些窗口能接收到消息事件,指定后只有对应origin下的窗口才可以接收到消息,设置为通配符”*”表示可以发送到任何窗口,但通常处于安全性考虑不建议这么做.如果想要发送到与当前窗口同源的窗口,可设置为”/”

transfer | 可选属性

是一串和message同时传递的**Transferable**对象,这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权.

Message事件

window.addEventListener("message", receiveMessage, false) ;
function receiveMessage(event) {
var origin= event.origin;
console.log(event);
}

data 包含任意字符串数据,由原始脚本发送
origin 一个字符串,包含原始文档的方案、域名以及端口(如:http://domain.example:80)
lastEventId 一个字符串,包含了当前的消息事件的唯一标识符。
source 原始文件的窗口的引用。更确切地说,它是一个WindowProxy对象。
ports 一个数组,包含任何MessagePort对象发送消息。

常用实例

1.父页面接收子页面内容,这是最常用的,比如iframe自适应、模态弹窗

<body>
<h1>父窗口</h1>
<iframe id="childIframe" src="http://www.wpbars.com/files/childMessage.html"></iframe>
<div id="content"></div>
<script>
	window.addEventListener("message", function( event ) {
         // 把子窗口发送过来的数据显示在父窗口中
       document.getElementById("content").innerHTML=event.data;
     }, false );
</script>
</body>
<body>
	<h1>子窗口</h1>
	<script>
		top.postMessage('子窗口发出的内容','*');
	</script>
</body>

演示

2.父页面传送数据给子页面

<body>
<h1>父窗口HTML代码</h1>
<iframe id="childIframe" src="http://www.wpbars.com/files/childMessage3.html"></iframe>
<script>
	var iframe = document.getElementById('childIframe');
	iframe.onload = function(){
		iframe.contentWindow.postMessage('父页面发出的内容','*');
	}
</script>
</body>
<body>
	<h1>子窗口</h1>
	<div id="content"></div>
	<script>
		window.addEventListener("message", function( event ) {
         // 把子窗口发送过来的数据显示在父窗口中
       document.getElementById("content").innerHTML=event.data;
     }, false );
	</script>
</body>

演示

安全验证

使用message接收数据时,几乎包含了所有应该有的信息。甚至data中可以包含object,出于安全考虑可以域的校验,数据规则的校验安全校验,如下代码

function getMessge(callback){
	window.addEventListener('message', function (event) {

		//校验函数是否合法
		var checkMessage = function () {
			// 只获取需要的域,并非所有都可以跨域
			if (event.origin != "need domain") {
				return false;
			}


			var message = event.data;
			// 传输数据类型校验
			if (typeof(message) !== 'object') {
				return false;
			}

			// message 的rule中包含xxx则为xxx需要字段。
			return message.rule === "xxx";
		};

		if (checkMessage()) {
			// 通过校验进行相关操作
			callback && callback(event);
		}
	});
}

iframe高度自适应

<body>
<h1>父页面</h1>
<iframe src="http://www.wpbars.com/files/childMessage.html" id='myframe' name="myframe" allowTransparency="true" scrolling="no" width="100%" height="100%" frameborder="0"></iframe>
<script>
var p = document.getElementById('myframe');
	window.postMessage && (window.attachEvent ? window.attachEvent("onmessage",
		function(a) {
			if (0 === parseInt(a.data, 10)) p.height = "100%",
				document.getElementById("myframe").contentWindow.postMessage("getHeight", "*");
			else if (a.data.toString().indexOf("px") > 0) {
				var b = parseInt(a.data, 10);
				isNaN(b) || (p.height = b + "px")
			}
		}) : window.addEventListener("message",
		function(a) {
			if (0 === parseInt(a.data, 10)) p.height = "100%",
				document.getElementById("myframe").contentWindow.postMessage("getHeight", "*");
			else if (a.data.toString().indexOf("px") > 0) {
				var b = parseInt(a.data, 10);
				isNaN(b) || (p.height = b + "px")
			}
		},
		!1));
</script>
</body>
<body>
<h1>子页面</h1>
<script type="text/javascript">
	function calcPageHeight (doc) {
		var	sHeight = Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight);
		
		return sHeight.toString();
	};
	calcPageHeight(document);

	window.onload = function () {
		if (top && top.location !== self.location) {
			if ((navigator.appName === 'Microsoft Internet Explorer' && navigator.appVersion .split(';')[1].replace(/[ ]/g, '') === 'MSIE7.0') ||
				(navigator.appName === 'Microsoft Internet Explorer' && navigator.appVersion .split(';')[1].replace(/[ ]/g, '') === 'MSIE6.0')) {
				if (top.name) {
					top.name = calcPageHeight(document);
				}
			}
			else {
				if (top.postMessage) {
					window.attachEvent ? window.attachEvent('onmessage', function (event) {
						top.postMessage(calcPageHeight(document) + 'px', '*');
					}) : window.addEventListener('message', function (event) {
						top.postMessage(calcPageHeight(document) + 'px', '*');
					}, false);
					top.postMessage(0, '*');
				}
				else if (top.name) {
					top.name = calcPageHeight(document);
				}
			}
		}
	};
</script>
</body>

演示

兼容性

16579fcb079d835
查阅 caniuse上可得知,在iframe与iframe之间,postMessage支持IE8及以上,上面的这个iframe自适应在ie8中是没问题的。


关注我

我的微信公众号:前端开发博客,在后台回复以下关键字可以获取资源。

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF
  • 回复「Vue脑图」获取 Vue 相关脑图
  • 回复「思维图」获取 JavaScript 相关思维图
  • 回复「简历」获取简历制作建议
  • 回复「简历模板」获取精选的简历模板
  • 回复「加群」进入500人前端精英群
  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。
  • 回复「知识点」下载高清JavaScript知识点图谱

每日分享有用的前端开发知识,加我微信:caibaojian89 交流