之前一提到跨域,都是前端到后臺的問題.
其實(shí),在網(wǎng)頁中嵌套非同源的iframe也存在跨域的問題.
比如,在你自己的頁面里利用 iframe 嵌套百度的網(wǎng)頁,兩個(gè)頁面存在通信的話,就存在跨域的問題.
對.如果不存在通信,就不存在所謂的跨域問題.
iframe 是干嘛的?
在當(dāng)前網(wǎng)頁中利用 iframe
可以嵌入另一個(gè)完整的網(wǎng)頁.
這個(gè)就是 iframe
干的事情.
這樣做的意義在哪呢?為什么我要在我的一個(gè)網(wǎng)頁里嵌入另外一個(gè)網(wǎng)頁?
- 有時(shí)候是廣告.(別人開發(fā)的網(wǎng)頁,放在我們網(wǎng)頁指定的位置)
- 有時(shí)候是業(yè)務(wù)邏輯太復(fù)雜,需要單獨(dú)的一個(gè)網(wǎng)頁去承載.于是把這個(gè)比較復(fù)雜的邏輯網(wǎng)頁就放到網(wǎng)頁中了.
其實(shí)都是我瞎謅的.為了讓自己能夠更快的理解這個(gè)玩意,編就編吧.
iframe 嵌入自己的網(wǎng)頁.
開發(fā)web的時(shí)候,使用iframe嵌入自己的網(wǎng)頁.
這里嵌入自己的網(wǎng)頁潛臺詞就是兩個(gè)網(wǎng)頁是同源的.
我們自己開發(fā)的網(wǎng)頁,當(dāng)然是部署在自己的服務(wù)器上.
不出意外的話,那協(xié)議
+域名
+端口號
都是一致的.
所以,它們是同源.
iframe 的基本語法如下.
<iframe src="2.html" height="300" width="500" id='demo' name="demo"></iframe>
項(xiàng)目結(jié)構(gòu):
同源 3 個(gè)靜態(tài)頁面. 1.html
2.html
3.html
- 在 1.html 中嵌入 2.html
- 在 2.html 中嵌入 3.html
1.html
<body>
<h1 id="h1">我是1.html網(wǎng)頁</h1>
<iframe src="2.html" height="300" width="500" id='demo' name="demo"></iframe>
</body>
2.html
<body>
<h1 id="h1">我是2.html網(wǎng)頁</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>
嵌入完畢之后呢?
光顯示的話,也就到這結(jié)束了.
如果需要操作同源下嵌套的iframe.
可以按照以下步驟.
1.獲取指定的iframe
1.html
<!--同源嵌套2.html-->
<iframe src="2.html" height="300" width="500" id='demo'></iframe>
let iframe = document.getElementById('demo')
接跟獲取一個(gè)dom元素一樣.利用 getElementById('demo')
即可.
2.獲取iframe內(nèi)部一些關(guān)鍵屬性
對于一個(gè)完整的html頁面來說.
它有window,也有document.
對于嵌套的iframe來說,也不例外.
但是指的注意的是,一定要在等待iframe這個(gè)嵌套頁面加載完畢之后,在去進(jìn)行獲取.
iframe頁面是異步加載的.
// 等待iframe嵌套的頁面加載完畢
iframe.onload = function () {
let iframeWindow = iframe.contentWindow
let iframeDocument = iframe.contentDocument
}
3. 在同源的情況下
iframe 可以等同于一個(gè)普通的DOM節(jié)點(diǎn)(不過它是異步加載的)
拿到這個(gè)iframe的document和window之后
iframe.onload = function () {
let iframeWindow = iframe.contentWindow
let iframeDocument = iframe.contentDocument
iframeDocument.getXXXX ==== 獲取或修改嵌套iframe的dom結(jié)構(gòu).就像操作自己的document一樣
iframeWindow.xxxx === 獲取嵌套iframe的方法或者屬性或者對象.就像操作自己的window一樣.
}
幾個(gè)注意點(diǎn):
iframe 嵌套獲取只能獲取一層.(比如
1.html
嵌套iframe(2.html)
->2.html
嵌套iframe(3.html)
.那么1.html
只能獲取到2.html
2.html
只能獲取到3.html
window.top
獲取當(dāng)前iframe嵌套層級的最頂級(這里是1.html)window.parent
獲取當(dāng)前iframe的上一層級(2.html
就是1.html
,3.html
就是2.html
)
同源iframe嵌套總結(jié):
- 可以把嵌套的iframe理解成一個(gè)普通的大DOM節(jié)點(diǎn).
- 它是異步加載的必須等待onload執(zhí)行完成.
- 指向完成后拿到 contentWindow 和 contentDocument 之后,就可以無縫的操作了.
- 注意,iframe只能拿一層.
跨域的iframe通信
兩個(gè)頁面
- 一個(gè)
1.html
鏈接地址為:http://127.0.0.1:12345/1.html
- 一個(gè)
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
端口號不一致時(shí),并不影響iframe嵌套.
(經(jīng)常瞎搞在自己頁面里嵌套一個(gè)baidu首頁)
但是會影響它倆之前的數(shù)據(jù)跨域請求.
比如,按照同源的方式,去操作iframe的頁面,會得到這樣一個(gè)提示.
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控制臺輸出
發(fā)現(xiàn) document
獲取不到.但是獲取的到window
.
window上有設(shè)置了一個(gè)全局對象 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)
}
很明顯的錯(cuò)誤提示,操作跨域了.
不能訪問跨域iframe的window上的全局屬性和方法.
拿不到document
(這里為null)了,就更加不能操作dom
元素了.
所以,如果嵌套的iframe跨域了,默認(rèn)情況下只能加載下來看,不能做任何其他的操作.
利用postMessage進(jìn)行iframe跨域通信
方式一:同一級域名不同二級域名
比如 www.a.com/index.html
和 api.a.com/index.html
由于它們的的一級域名一一致.
可以利用 document.domain
進(jìn)行跨域操作.
a.html
document.domain = 'a.com'
b.html
document.domain = 'b.com'
雙方都設(shè)置同樣的域之后,就可以像同源非跨域的iframe那樣操作了.
方式二.使用postMessage
看了很多博客關(guān)于postMessage方法的使用.
大致說的都是:
如果嵌套的iframe存在跨域,那么就可以使用postMessage進(jìn)行通信.
于是心想,這也太簡單了吧.
就開始吭哧吭哧寫代碼.
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>
現(xiàn)在,我想讓 1.html 跨域的給 2.html 傳遞數(shù)據(jù).
于是在 1.html 中.
window.postMessage('1.html的數(shù)據(jù)','http://127.0.0.1:50874/')
在 4.html 中
window.addEventListener('message', function (e) {
console.log(e.data)
},false)
想著非常完美,也太簡單了.
執(zhí)行瀏覽器.
發(fā)現(xiàn)報(bào)錯(cuò)了.
回想一下:
- 有
1.html
-
1.html
中利用iframe
嵌套了2.html
- 它倆的屬于不同的域(端口號不同)
- 現(xiàn)在我想從
1.html
傳遞數(shù)據(jù)到2.html
. - 在
1.html
里面使用window.postMessage()
發(fā)現(xiàn)報(bào)錯(cuò)了.
問題出在哪?
重新查看API文檔之后,發(fā)現(xiàn)理解是錯(cuò)的.
本質(zhì)上利用 postMessage 跨域,不是 1.html
給 2.html
發(fā)數(shù)據(jù).
應(yīng)該是是 2.html
給 2.html
發(fā)數(shù)據(jù)
體現(xiàn)在代碼上應(yīng)該是就是:
1.html -> http://127.0.0.1:12345/1.html
window.onload = function () {
let frame = document.getElementById('frame')
// 相當(dāng)于還是自己在給自己傳啊!!!
document.getElementById('text').addEventListener('input', function () {
frame.contentWindow.postMessage(this.value, 'http://127.0.0.1:50874/')
}, false)
}
傳遞數(shù)據(jù)的是 frame.contentWindow.postMessage
而不是想當(dāng)然的 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>
<!-- 設(shè)置一個(gè)按鈕,點(diǎn)擊按鈕往跨域的4.html發(fā)送數(shù)據(jù) -->
<button class="postMessage">postMessage</button>
<!-- 用于接受4.html跨域提交過來的數(shù)據(jù) -->
<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跨域發(fā)送數(shù)據(jù)
let postMessageButton = document.querySelector('.postMessage')
postMessageButton.addEventListener('click', function () {
// 這里是使用iframeWindow.也就是iframe自己的window發(fā)送數(shù)據(jù).
// 而不是使用window.
// 就相當(dāng)于使用 postMessage 其實(shí)本質(zhì)上還是自己在給自己發(fā)數(shù)據(jù).
iframeWindow.postMessage('1.html發(fā)送過來的數(shù)據(jù)','http://127.0.0.1:56434') // 第二個(gè)參數(shù),指定4.html的域名
}, false)
// 用于接收 4.html提交回來的數(shù)據(jù)
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 發(fā)送過來的數(shù)據(jù) -->
<p class="result"></p>
<!-- 設(shè)置一個(gè)按鈕給 1.html 發(fā)送數(shù)據(jù) -->
<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發(fā)送過來的數(shù)據(jù)','http://127.0.0.1:12345/') // 注意,這里由于我們知道 4.html 被 1.html 嵌套了,所以使用 window.parent 可以拿到 1.html 環(huán)境中的window.
// 由于我們也知道 1.html 是當(dāng)前iframe嵌套層級中的最頂層,也可以使用window.top.postMessage() ....
}, false)
</script>
查看結(jié)果:
最后總結(jié):
- 跨域不光是前臺都后臺的跨域.
- iframe嵌套不同源的資源也存在跨域.
- 如果一級域名相同,可以使用
document.domain
進(jìn)行跨域操作. - 如果域名完全不同,可以使用
postMessage
進(jìn)行跨域.-
postMessage
傳遞數(shù)據(jù),本質(zhì)上仍然是自己給自己傳 - 在給某個(gè)iframe傳遞數(shù)據(jù)時(shí),必須首先拿到當(dāng)前iframe的contentWindow.然后在
contentWindow
上使用postMessage
傳遞數(shù)據(jù).
-
- 如果一級域名相同,可以使用