聊聊iframe的跨域通信问题

6,388 阅读3分钟

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

前言

iframe平时用的不多,所以今天看到有位小伙伴说,他用的iframe出现跨域问题。 我听完有点不知所措,因为我们平时遇到的跨域问题一般是请求的跨域比较多。所以对于iframe的跨域了解的不多,今天就来好好复习一下。

iframe的跨域

iframe主要是用来嵌套第三方页面的,比如嵌入地图,新闻页面等等。只要把iframe的src的设置成第三方页面的页面地址,就可以显示了。

但是如果第三方页面跟主页面的域名不是同源的话,也就是不满足以下条件之一:

  • 域名一致
  • 协议一致
  • 端口一致

就属于跨域了,这时候第三方页面可以正常显示,但是如果需要跟它通信就不可以了,比如说更改第三方页面的样式,调用它的方法,或者第三方页面调用主页面的方法或者修改样式。

下面我用代码来模拟下:

主页面a.html:

  <div>
    我是a页面
    <iframe src="http://127.0.0.1:8081/b.html" frameborder="0" name="iframe"></iframe>
  </div>

iframe页面b.html

  <div>
    我是b页面
  </div>
  <script>
    console.log(window.parent.document) // 打印a页面的document
  </script>

开2个终端运行,设置端口不一样,模拟跨域

可以看到

image.png

解决方法

postMessage方法

html5新增的跨页面通信方法

用法如下:

目标页面:

监听message事件

window.addEventListener('message', func)

触发页面:

targetWindow.postMessage(message, targetOrigin, [transfer]);

targetWindow对应的是目标页面的window对象

参数:

  • message

    需要传递的数据

  • targetOrigin

    目标页面的域名地址,等同于location.origin

  • transfer

    可选参数,网上的解释不好理解,一般用不上(知道的朋友可以解释)

代码如下:

主页面a.html:

  <div>
    我是a页面
    <iframe src="http://127.0.0.1:8081/b.html" frameborder="0"></iframe>
  </div>
  <script>
    window.addEventListener('message', (res) => {
      console.log('接受到b页面传来的数据', res)
    })
    
    window.onload = () => {
      const iframe = document.querySelector('iframe')
      // 需要等待iframe加载完再postMessage
      iframe.contentWindow.postMessage({ text: '你好,我是页面a' }, 'http://127.0.0.1:8081')
    }
    </script>
  </div>

iframe页面b.html:

  <div>
    我是b页面
  </div>
  <script>
    window.parent.postMessage({ text: '你好,我是页面b' }, 'http://127.0.0.1:8080')
    
    window.addEventListener('message', (res) => {
      console.log('接受到a页面传来的数据', res)
    })
  </script>

双方都能接收和发送数据

结果如下:

image.png

设置document.domain

这个方法是用于域名的公共域的,通过设置这个document.domain,可以解决iframe的跨域问题。

不过这个只能解决主域一致,子域不同的跨域问题。

比如你主域是aaa.com, 子域名是test.aaa.com。这是有跨域问题的。

这时候我们可以设置document.domain等于aaa.com来解决,需要在主页面iframe页面一起设置才生效。

a页面:

  <div>
    我是a页面
    <iframe src="http://test.aaa.com/b.html" frameborder="0"></iframe>
  </div>
  <script>
    document.domain = 'aaa.com'
  </script>

b页面:

  <div>
    我是b页面
  </div>
  <script>
    document.domain = 'aaa.com'
    console.log(window.parent.document) // 打印需要设置之后
  </script>

我是把我本地的host文件改了,127.0.0.1 指向了 aaa.com, 内网ip指向了test.aaa.com

打印成功

image.png

window.name + 中间页

在同一个窗口,window.name的值如果设置后,只要窗口还没有关闭,不管页面的url怎么跳转,window.name都不变。

所以,建一个c页面,c页面跟a页面是同域的,a页面用iframe嵌入b页面,在b页面把数据通过window.name赋值,然后b页面通过location.href = c页面的url跳转到c页面,这样子保持a页面和iframe的地址(c页面)是同域的,a页面可以通过iframe.contentWindow.name拿到window.name的值。

在a页面通过监听iframe的onload事件,设立个标志位,onload事件会执行2次,第一次是b页面加载成功,第二次是c页面加载成功,可以通过iframe.contentWindow.name访问window.name的值。

location.hash + hashchange事件 + 中间页

  • 建一个c页面,c页面跟a页面是同域的,a页面用iframe嵌入b页面的url,然后在url再加上hash,这就是传入的数据(因为这样才能触发下面的hashchange事件)

  • b页面监听hashchange事件,触发后取得hash值(传入的数据),然后载入c页面的url(在url加上hash,这就是返回给a页面的数据)

  • c页面和a页面是同域的,c页面可以通过window.parent.parent找到a页面,然后把数据传回给a页面(调用a页面的方法)。

总结

以上就是解决iframe跨域问题的几种方式。

最推荐是第一种,通过postMessage方式。

如果主域一致,可以设置document.domain

感谢你们的阅读。