iframe -- postMessage

之前一提到跨域,都是前端到后台的问题.

其实,在网页中嵌套非同源的iframe也存在跨域的问题.

比如,在你自己的页面里利用 iframe 嵌套百度的网页,两个页面存在通信的话,就存在跨域的问题.

对.如果不存在通信,就不存在所谓的跨域问题.


iframe 是干嘛的?

在当前网页中利用 iframe 可以嵌入另一个完整的网页.

这个就是 iframe 干的事情.

这样做的意义在哪呢?为什么我要在我的一个网页里嵌入另外一个网页?

  • 有时候是广告.(别人开发的网页,放在我们网页指定的位置)
  • 有时候是业务逻辑太复杂,需要单独的一个网页去承载.于是把这个比较复杂的逻辑网页就放到网页中了.

其实都是我瞎诌的.为了让自己能够更快的理解这个玩意,编就编吧.


iframe 嵌入自己的网页.

开发web的时候,使用iframe嵌入自己的网页.

这里嵌入自己的网页潜台词就是两个网页是同源的.

我们自己开发的网页,当然是部署在自己的服务器上.

不出意外的话,那协议+域名+端口号都是一致的.

所以,它们是同源.

iframe 的基本语法如下.

<iframe src="2.html" height="300" width="500" id='demo' name="demo"></iframe>

项目结构:

image.png

同源 3 个静态页面. 1.html 2.html 3.html

  • 在 1.html 中嵌入 2.html
  • 在 2.html 中嵌入 3.html
image.png

1.html

<body>
  <h1 id="h1">我是1.html网页</h1>
  <iframe src="2.html" height="300" width="500" id='demo' name="demo"></iframe>
</body>

2.html

<body>
  <h1 id="h1">我是2.html网页</h1>
  <iframe src="3.html" width="200" height="200" id="demo2" name="demo2"></iframe>
</body>

3.html

<body>
  <h1 id="h1">我是3.html</h1>
</body>

嵌入完毕之后呢?

光显示的话,也就到这结束了.

如果需要操作同源下嵌套的iframe.
可以按照以下步骤.

1.获取指定的iframe

1.html
<!--同源嵌套2.html-->
<iframe src="2.html" height="300" width="500" id='demo'></iframe>

let iframe = document.getElementById('demo')

接跟获取一个dom元素一样.利用 getElementById('demo') 即可.

2.获取iframe内部一些关键属性

对于一个完整的html页面来说.
它有window,也有document.

对于嵌套的iframe来说,也不例外.

但是指的注意的是,一定要在等待iframe这个嵌套页面加载完毕之后,在去进行获取.

iframe页面是异步加载的.

// 等待iframe嵌套的页面加载完毕
iframe.onload = function () {
    let iframeWindow = iframe.contentWindow
    let iframeDocument = iframe.contentDocument
}

3. 在同源的情况下

iframe 可以等同于一个普通的DOM节点(不过它是异步加载的)
拿到这个iframe的document和window之后

iframe.onload = function () {
    let iframeWindow = iframe.contentWindow
    let iframeDocument = iframe.contentDocument
    
    iframeDocument.getXXXX ==== 获取或修改嵌套iframe的dom结构.就像操作自己的document一样
    iframeWindow.xxxx === 获取嵌套iframe的方法或者属性或者对象.就像操作自己的window一样.
}

几个注意点:

  • iframe 嵌套获取只能获取一层.(比如1.html 嵌套 iframe(2.html) -> 2.html 嵌套 iframe(3.html).那么 1.html只能获取到2.html 2.html只能获取到3.html

  • window.top 获取当前iframe嵌套层级的最顶级(这里是1.html)

  • window.parent 获取当前iframe的上一层级(2.html就是1.html,3.html就是2.html)

image.png

同源iframe嵌套总结:

  • 可以把嵌套的iframe理解成一个普通的大DOM节点.
  • 它是异步加载的必须等待onload执行完成.
  • 指向完成后拿到 contentWindow 和 contentDocument 之后,就可以无缝的操作了.
  • 注意,iframe只能拿一层.

跨域的iframe通信

两个页面

  • 一个 1.html 链接地址为: http://127.0.0.1:12345/1.html
  • 一个 4.html 链接地址为: http://127.0.0.1:50874/iframe-01/4.html
1.html -> http://127.0.0.1:12345/1.html
<body>
   <iframe src="http://127.0.0.1:50874/iframe-01/4.html" frameborder="0" id='frame'></iframe>
</body>
4.html ---> http://127.0.0.1:50874/iframe-01/4.html
<body>
    <h1>我是4.html 端口号50874</h1>
</body>
<script>
    // 在 4.html定义的全局对象 window.obj
    var global4Obj = {
    name: '李四'
  }
</script>

它俩的端口号不一致.

12345 | 50874

端口号不一致时,并不影响iframe嵌套.
(经常瞎搞在自己页面里嵌套一个baidu首页)

但是会影响它俩之前的数据跨域请求.

比如,按照同源的方式,去操作iframe的页面,会得到这样一个提示.

1.html -> http://127.0.0.1:12345/1.html
let iframe = document.getElementById('frame')
  iframe.onload = function () {
    const window = iframe.contentWindow
    const document = iframe.contentDocument
    console.log('window',window)
    console.log('document',document)
  }

1.html控制台输出

image.png

发现 document 获取不到.但是获取的到window.

window上有设置了一个全局对象 obj

于是输出:

  let iframe = document.getElementById('frame')
  iframe.onload = function () {
    const window = iframe.contentWindow
    const document = iframe.contentDocument
    console.log('window',window)
    console.log('document',document)
    console.log(window.obj.name)
  }
image.png

很明显的错误提示,操作跨域了.
不能访问跨域iframe的window上的全局属性和方法.
拿不到document(这里为null)了,就更加不能操作dom元素了.

所以,如果嵌套的iframe跨域了,默认情况下只能加载下来看,不能做任何其他的操作.


利用postMessage进行iframe跨域通信

方式一:同一级域名不同二级域名

比如 www.a.com/index.htmlapi.a.com/index.html

由于它们的的一级域名一一致.

可以利用 document.domain 进行跨域操作.

a.html
document.domain = 'a.com'
b.html
document.domain = 'b.com'

双方都设置同样的域之后,就可以像同源非跨域的iframe那样操作了.

方式二.使用postMessage

看了很多博客关于postMessage方法的使用.

大致说的都是:

如果嵌套的iframe存在跨域,那么就可以使用postMessage进行通信.

于是心想,这也太简单了吧.

就开始吭哧吭哧写代码.

image.png
1.html -> http://127.0.0.1:12345/1.html
<body>
  <!-- <b>12345</b> -->
  <h1>我是1.html</h1>
  <p></p>
  <iframe src="http://127.0.0.1:50874/iframe-01/4.html" frameborder="0" id='frame'></iframe>
</body>

4.html ->http://127.0.0.1:50874/iframe-01/4.html

<body>
  <h2>我是4.html</h2>
  <p></p>
</body>

现在,我想让 1.html 跨域的给 2.html 传递数据.

于是在 1.html 中.

window.postMessage('1.html的数据','http://127.0.0.1:50874/')

在 4.html 中

  window.addEventListener('message', function (e) {
    console.log(e.data)
  },false)

想着非常完美,也太简单了.

执行浏览器.

image.png

发现报错了.

回想一下:

  • 1.html
  • 1.html 中利用 iframe 嵌套了 2.html
  • 它俩的属于不同的域(端口号不同)
  • 现在我想从 1.html 传递数据到 2.html.
  • 1.html 里面使用 window.postMessage() 发现报错了.

问题出在哪?

重新查看API文档之后,发现理解是错的.

本质上利用 postMessage 跨域,不是 1.html2.html 发数据.

应该是是 2.html2.html 发数据

体现在代码上应该是就是:

1.html -> http://127.0.0.1:12345/1.html
window.onload = function () {
    let frame = document.getElementById('frame')
    // 相当于还是自己在给自己传啊!!!
    document.getElementById('text').addEventListener('input', function () {
      frame.contentWindow.postMessage(this.value, 'http://127.0.0.1:50874/')
    }, false)
  }

传递数据的是 frame.contentWindow.postMessage
而不是想当然的 window.postMessage

测试一下想法.

1.html中 --> http://127.0.0.1:12345/1.html

1.html -> http://127.0.0.1:12345/1.html

<body>
  <!-- <b>12345</b> -->
  <h1>我是1.html</h1>
  <p></p>
  <iframe src="http://127.0.0.1:56434/iframe-01/4.html" width="500" height="300" id='frame'></iframe>
  <!-- 设置一个按钮,点击按钮往跨域的4.html发送数据 -->
  <button class="postMessage">postMessage</button>
  <!-- 用于接受4.html跨域提交过来的数据 -->
  <p class="result"></p>
</body>

1.html -> http://127.0.0.1:12345/1.html
<script>
// 第一步,要拿到iframe,这里主要是拿到iframe.contentWindow
  let iframe = document.getElementById('frame')
  let iframeWindow = null
  iframe.addEventListener('load', function () {
    console.log('iframe loaded')
    iframeWindow = iframe.contentWindow // 利用iframe.contentWindow 也就是4.html 的window对象.
  },false)
  
// 给4.html利用postMessage跨域发送数据
let postMessageButton = document.querySelector('.postMessage')
  postMessageButton.addEventListener('click', function () {
    // 这里是使用iframeWindow.也就是iframe自己的window发送数据.
    // 而不是使用window.
    // 就相当于使用 postMessage 其实本质上还是自己在给自己发数据.
    iframeWindow.postMessage('1.html发送过来的数据','http://127.0.0.1:56434') // 第二个参数,指定4.html的域名
  }, false)
  
  // 用于接收 4.html提交回来的数据
  const result = document.querySelector('.result')
  window.addEventListener('message', function (event) {
    result.innerText = event.data
    console.log(event.source)
    console.log(event)
  }, false)
</script>

在4.html中 --> ->http://127.0.0.1:50874/iframe-01/4.html

4.html -> http://127.0.0.1:50874/iframe-01/4.html

<body>
  <h2>我是4.html</h2>
  <p></p>
  <!-- 用于接受 1.html 使用 postMessage 发送过来的数据 -->
  <p class="result"></p>
  <!-- 设置一个按钮给 1.html 发送数据 -->
  <button class="postMessage">postMessage</button>
</body>

4.html->http://127.0.0.1:50874/iframe-01/4.html

<script>
   let result = document.querySelector('.result')
  window.addEventListener('message', function (e) {
    result.innerText = e.data
  },false)

  let postMessageButton = document.querySelector('.postMessage')
  postMessageButton.addEventListener('click', function () {
    window.parent.postMessage('4.html发送过来的数据','http://127.0.0.1:12345/') // 注意,这里由于我们知道 4.html 被 1.html 嵌套了,所以使用 window.parent 可以拿到 1.html 环境中的window.
    
    // 由于我们也知道 1.html 是当前iframe嵌套层级中的最顶层,也可以使用window.top.postMessage() ....
  }, false)   
</script>

查看结果:

image.png

最后总结:

  • 跨域不光是前台都后台的跨域.
  • iframe嵌套不同源的资源也存在跨域.
    • 如果一级域名相同,可以使用document.domain 进行跨域操作.
    • 如果域名完全不同,可以使用 postMessage 进行跨域.
      • postMessage 传递数据,本质上仍然是自己给自己传
      • 在给某个iframe传递数据时,必须首先拿到当前iframe的contentWindow.然后在contentWindow上使用postMessage 传递数据.

码云code

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容