此方案已有新版本, 请查看 《iframe 跨域通信的通用解决方案-第二弹!(终极解决方案)》。本文章可做技术学习供继续交流。
一、背景
在这个 Web 页面越来越丰富的时代,页面通过 iframe 嵌入其他的页面也越来越常见。但由于浏览器同源策略的限制,不同域之间属性和操作是无法直接交互的,所以在这个时候,开发者多多少少需要一些方案来突破这些限制。跨域问题涉及的地方也很多,如文档之间的消息通信、ajax 请求、Cookie 等,本文讨论的是 iframe 和父页面的消息通信。
二、现状
目前网上也可以找到各种解决方案(少说都有 10+个,有兴趣的话可以去看看),对于现代浏览器来说,原生的 postMessage API 一定是不二的选择,所以各种方案的不同点均在于 IE 6、7 中的处理(不用兼容 IE6、7 的同志可以去看其他文章了)。当然这么多方案有各种优缺点,甚至有些只支持单向跨域,个人觉得实际意义不大。另外一些方案需要 proxy.html 这样的代理页面做中转,但是涉及服务器上的部署,并且对于多方合作来说还是有些麻烦。
三、思路
虽然不再复述现有的各种方案,但还是想交待一点上下文。相信网上看到最多方案就是利用 location.hash 或是 window.name 进行 iframe 的跨域通信:
- location.hash 会直接暴露在 URL 里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验,所以不做考虑。另外由于 URL 大小的限制,支持传递的数据量也不大。
- window.name 相比来讲就好很多了,支持 2M 的数据量,并且当 iframe 的页面跳到其他地址时,其 window.name 值保持不变,副作用可以说是最小的。
讲到这思路也比较清晰了,咱们就用 window.name 呗,但问题又来了:只有两个页面同域时才能访问 window.name。这个问题还好,只要把 iframe 改为与父页面同域就可以了。这又衍生了新的问题:这不是意味着只能单向通信了吗,iframe 怎么向父页面发消息(不可能去改父页面的 location 吧)?在暗骂坑爹的同时偶然发现了一个很神奇的方法,就是想访问一个 iframe 的 window.name 时,只要将其 location 改为 ‘about:blank’ 即可,屡试不爽~没错这个 “特性” 可以视为 IE6/7 的一项安全性问题,但利用这个特性来进行跨域通信并没有实际的安全风险。
具体的实现见下图,在 iframe 的内部再创建一个 iframe(我们称之为信使),父子页面轮询信使的 window.name,父子页面各自使用变量保存 window.name,轮询时发现有变化即被视为收到消息。基本原理就是这么简单,我们继续..
图 1
作为一个通用的解决方案,我们的目标是提供一个 js 文件,封装通信的接口,需要通信的页面只要加载 js 文件就行。但在封装前,需要考虑更复杂一点的情况:当父子页面双方频率较高地双向通信时,如何进行支持?按照上述的方案,信使的 window.name 并没有读写锁的概念,这意味着消息很容易乱掉或被漏掉。所以更好的方案应该是:创建两个信使,分别负责"父--> 子"和"子--> 父"的消息传递,并且为了防止消息被冲掉,发送消息时会维护一个消息队列,在取消息时处理消息队列里的所有消息。见图 2。
图 2
四、封装
最后的封装就是加入了 postMessage API 的检测,另外也要判断是否为跨域,这样就满足了所有 iframe 通信的情况了。这里实现的信使只负责消息的监听和发送,所以在使用上是非常简单的:
1 2 3 4 5 6 7 8 9 10 11 |
// 父页面中 // 初始化信使, 告知与其交互的iframe引用 var messenger = Messenger.initInParent(iframeEl); // 监听消息 messenger.onmessage = function (data) { ... }; // 发送消息 messenger.send(message); |
1 2 3 4 5 6 7 8 9 10 11 |
// iframe中 // 初始化信使 var messenger = Messenger.initInIframe(); // 监听消息 messenger.onmessage = function (data) { ... }; // 发送消息 messenger.send(message); |
具体使用可以参考下方的 demo : )
五、总结
虽然国内也有人提过使用"about:blank"进行 iframe 通信的,但是代码的封装和可读性都不是太好,本方案是一日本人所提出,我觉得处理的很好,所以就拿出来和大家分享下。虽然尝试过优化轮询那一块,但暂时无果,有兴趣的朋友可以一起研究下~
DEMO:点击这里
脚本下载:http://biqing.alloyteam.com/lab/messenger/messenger.js
GitHub:https://github.com/biqing/MessengerJS
iframe跨域通信的通用解决方案-第二弹!(终极解决方案) | Web开源笔记-专注Web开发技术,分享Web开发技术与资源 2014 年 3 月 6 日
[…] 一年前,我发过一篇关于跨文档通信方案的文章《iframe 跨域通信的通用解决方案》,提供了一种基于创建 iframe 与轮询 window.name 的方案。 […]
iframe跨域通信的通用解决方案-第二弹!(终极解决方案) | Tencent AlloyTeam 2013 年 11 月 29 日
[…] 一年前,我发过一篇关于跨文档通信方案的文章《iframe 跨域通信的通用解决方案》,提供了一种基于创建 iframe 与轮询 window.name 的方案。 […]
heyi 2013 年 10 月 12 日
特意上来顶一下楼主..
楼主精神可嘉. 以后有机会 , 还请多多指教
大七 2013 年 9 月 27 日
在父页面设置
document.domain='alloyteam.com'
后,就没法通信了,有解决方案吗?TAT.Johnny 2013 年 11 月 29 日
方案已经有了,见最新更新的文章
绘啊 2013 年 9 月 3 日
厉害呀!就在找个这个 iframe 的解决方法!!在感谢啦!
aram 2013 年 8 月 9 日
很受启发,谢谢分享
artwl 2013 年 7 月 23 日
发现在某些环境的 IE6 下不能通信
逝影落枫 2012 年 10 月 31 日
实际项目中使用发现,在 ie7 下有问题。不知道大家有没有遇到。
wbpmrck 2012 年 10 月 31 日
实际测试可用,非常感谢。分析一个问题把问题的来龙去脉讲的很清楚,值得赞一个
豆奶 2012 年 10 月 30 日
貌似对于 http 内嵌 https 的跨域问题无能为力,博主可有纯前端的解决方案?
TAT.Johnny 2013 年 11 月 29 日
请见最新文章的解决方案 :)
jh7086 2012 年 10 月 30 日
是个好办法,学习了
Lanston 2012 年 9 月 7 日
想问问最外层的页面是如何访问不同域的里面的 blank 的 iframe 的 window.name 的?
johnny 2012 年 9 月 7 日
可以看下源码的 tryReceive 方法, 对拿 iframe 的 window.name 做 try/catch, 如果抛出异常的话就把 iframe 的 location 设为”about:blank”, 这时再去拿 iframe 的 window.name 就可以拿到了
qianke 2012 年 9 月 3 日
ie6 下不能用
johnny 2012 年 9 月 3 日
看了下,Demo 在 IE6 下是正常的,不知你遇到的是什么问题?可以给下具体的 IE 版本
qianke 2012 年 9 月 3 日
6.0.2900
点了按钮没有反应。
我看了几遍都没看明白原理。能再仔细阐述一下吗?如何触发 iframe.html 提交请求。是自己一直轮询检查自己的 window.name?
wkylin 2012 年 9 月 24 日
IE6 确实不支持,版本号 6.0.29000.5512.xpsp.080413-2111
wkylin 2012 年 9 月 24 日
更正一下,有同事把 ie6 的设置给改了,默认设置是可以支持的。
johnny 2012 年 10 月 10 日
了解一下, 具体是设置了什么?
johnny 2012 年 9 月 4 日
先在输入框输入字符串, 再点按钮发送消息, 发送的消息将会在对方的消息区显示出来.
原理是父页面和 iframe 同时分别轮询最内层的两个 iframe(也就是 messenger) 的 window.name
qianke 2012 年 9 月 6 日
轮询最内层的 iframe 的 window.name 这个轮询间隔是多少? 如果 window.name 几乎同时返回几个呢?会不会出现丢失情况。
johnny 2012 年 9 月 6 日
轮询间隔是 50ms, 为了防止丢失, messenger 维护了一个消息队列, 不会出现丢失情况.
匿名 2012 年 9 月 3 日
ie6 下不能用
唠叨下 2012 年 8 月 30 日
在运行文章末尾的那个 demo 的 时候,发现可以连续发送多个空字符串。是否属于 bug ?
johnny 2012 年 8 月 31 日
不是 bug 哟,可以看下 demo 的源码,这是消息处理函数的逻辑。当收到消息时,在消息区另起一行,同时将收到的消息展示出来。所以无论消息是否为空,都会另起一行的:)
wkylin 2012 年 8 月 29 日
思路清晰,最重要的是可以解决问题. 呵呵
qqfan 2012 年 8 月 24 日
赞~“屡试不爽”
bird 2012 年 8 月 14 日
图片是用什么画的啊
liupeng 2012 年 8 月 21 日
用的应该是 air 版的 Mockups 🙂 不知道对否
johnny 2012 年 8 月 23 日
哈哈, 是的, Balsamiq Mockups, 其实更适合画交互原型一些
TAT.Kinvix 2012 年 8 月 11 日
方案很不错,图配的很有专业性!
Chappell 2012 年 8 月 11 日
配图很清晰,
简洁易懂。
qqfan 2012 年 8 月 24 日
赞~