之前一提到跨域,都是前端到后台的问题.
其实,在网页中嵌套非同源的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
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
)
同源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.html
和 api.a.com/index.html
由于它们的的一级域名一一致.
可以利用 document.domain
进行跨域操作.
a.html
document.domain = 'a.com'
b.html
document.domain = 'b.com'
双方都设置同样的域之后,就可以像同源非跨域的iframe那样操作了.
方式二.使用postMessage
看了很多博客关于postMessage方法的使用.
大致说的都是:
如果嵌套的iframe存在跨域,那么就可以使用postMessage进行通信.
于是心想,这也太简单了吧.
就开始吭哧吭哧写代码.
image.png1.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.html
给 2.html
发数据.
应该是是 2.html
给 2.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
传递数据.
-
- 如果一级域名相同,可以使用
网友评论