背景
我們Web前端開發(fā)者一般都會有這樣的常識:同一瀏覽器打開了兩個不同域名的網(wǎng)頁,兩個頁面之間被沙箱隔了乳绕,因此是無法進(jìn)行數(shù)據(jù)交互的绞惦。這兩個頁面一旦嘗試用JS相互訪問資源,便會報錯洋措。
除非济蝉,你是借助服務(wù)端的能力(例如:AJAX或者WebSocket之類),間接地進(jìn)行通訊菠发。
瀏覽器限制站點跨域訪問是出于保護(hù)客戶端信息安全需要王滤,這一點無可厚非。但我們Web前端的某些開發(fā)場景滓鸠,確實有需求跨域做交互雁乡,怎么辦呢?
下面分享一下我的做法糜俗。
PS. 因為今天我想討論的是純前端的技術(shù)解決方案踱稍,依賴服務(wù)端解決辦法忽略掉。Ajax 和 WebSocket悠抹,你們都要出局啦珠月!
兩種跨域場景
Web跨域的場景通常有兩種:
- 1.
iframe
的內(nèi)嵌頁面與主窗口頁面之間的通訊 - 2.通過
window.open
打開新窗口與母窗口(opener)之間的通訊
我下面的案例會分別針對這兩種場景提供對應(yīng)的方案。
方案1:使用window.postMessage
的API
window.postMessage
是 html5 引入的新API楔敌,它允許來自不同源的腳本采用異步方式進(jìn)行有效的通信啤挎,可以實現(xiàn)跨文本文檔、多窗口卵凑、跨域消息傳遞庆聘,多用于窗口間數(shù)據(jù)通信胜臊。
從上面的介紹可以看出來,這算是w3c標(biāo)準(zhǔn)的跨域通信的解決方案伙判。
首先我們模擬一個跨域場景:假設(shè)我有兩個網(wǎng)頁象对,訪問地址分別為
- http://a.example.com/test.html
-
http://b.example.com/test.html
其中A作為主頁面,B作為子頁面(iframe嵌入或者window.open打開的頁面)澳腹。
1、處理iframe跨域的代碼示例
- A頁面代碼:
<html>
<head>
<title>Example CORS for iframe</title>
</head>
<body>
<button>Post Message</button>
<p></p>
<iframe src="http://b.example.com/test.html" width="500" height="300"></iframe>
</body>
<script>
const iframe = document.querySelector('iframe');
const button = document.querySelector('button');
const msg = document.querySelector('p');
button.addEventListener('click', () => {
iframe.contentWindow.postMessage('hello', '*');
});
window.addEventListener('message', (event) => {
msg.innerText = `Message from iframe: ${event.data}`;
});
</script>
</html>
- B頁面代碼:
<html>
<head>
<title>Inner Page</title>
</head>
<body>
<h1>Hi, this is an inner page</h1>
<p></p>
<div></div>
</body>
</html>
<script>
window.onload = ()=>{
setInterval(()=>{
var time = new Date().getTime().toString();
document.querySelector('p').innerHTML = `Current Timestamp:${time}`;
window.parent.postMessage(time,'*');
},1000);
window.addEventListener('message',(e)=>{
document.querySelector('div').innerHTML = `Message from Parent:${e.data}`;
});
}
</script>
2杨何、處理彈窗的跨域的代碼示例
- A頁面代碼:
<html>
<head>
<title>Example CORS for window.open</title>
</head>
<body>
<button>Post Message</button>
<p></p>
</body>
<script>
var childWindow;
function openWindow(url, width, height) {
var h = screen.height * height / 100;
var w = screen.width * width / 100;
var x = (screen.width - w) / 2;
var y = (screen.height - h) / 2;
return window.open(url, '_blank', 'width=' + w + ',height=' + h + ',top=' + y + ',left=' + x);
}
window.onload = function() {
childWindow = openWindow('http://b.example.com/test.html', 40, 30);
};
const button = document.querySelector('button');
const msg = document.querySelector('p');
button.addEventListener('click', () => {
childWindow.postMessage('hello', '*');
});
window.addEventListener('message', (event) => {
msg.innerText = `Message from iframe: ${event.data}`;
});
</script>
</html>
- B頁面代碼:
<html>
<head>
<title>Inner Page</title>
</head>
<body>
<h1>Hi, this is an inner page</h1>
<p></p>
<div></div>
</body>
</html>
<script>
window.onload = ()=>{
setInterval(()=>{
var time = new Date().getTime().toString();
document.querySelector('p').innerHTML = `Current Timestamp:${time}`;
window.opener.postMessage(time,'*');
},1000);
window.addEventListener('message',(e)=>{
document.querySelector('div').innerHTML = `Message from Parent:${e.data}`;
})
}
</script>
方案2:利用window.name
傳遞信息
處理iframe跨域的代碼示例(一)
- A頁面代碼:
<html>
<head>
<title>Example CORS for iframe</title>
</head>
<body>
<button>Post Message</button>
<p></p>
<iframe src="http://b.example.com/test.html" width="500" height="300"></iframe>
</body>
<script>
const iframe = document.querySelector('iframe');
const button = document.querySelector('button');
const msg = document.querySelector('p');
button.addEventListener('click', () => {
iframe.contentWindow.title = 'ToChild:hello';
});
setInterval(() => {
var name = iframe.contentWindow.name;
if(name && name.startsWith('ToParent:')) {
msg.innerHTML = 'Message from iframe:'+name.substring(9);
}
}, 200);
</script>
</html>
- B頁面代碼:
<html>
<head>
<title>Inner Page</title>
</head>
<body>
<h1>Hi, this is an inner page</h1>
<p></p>
<div></div>
</body>
</html>
<script>
window.onload = ()=>{
setInterval(()=>{
var name = window.name;
if(name && name.startsWith('ToChild:')){
var message = name.substring(8);
document.querySelector('div').innerHTML = `Message from Parent:${message}`;
}
var time = new Date().getTime().toString();
document.querySelector('p').innerHTML = `Current Timestamp:${time}`;
window.name=`ToParent:${time}`;
},1000);
}
</script>
-
注意:不出意外的話就要出意外了……
以上報錯證明:非辰此可惜,現(xiàn)代瀏覽器已經(jīng)把這個口子給堵上了危虱,這個方法僅適用于古代的瀏覽器羊娃。但是不要沮喪,既然我敢發(fā)出來埃跷,肯定還有別的招數(shù)蕊玷。雖然不完太美,能夠使用的范圍有限弥雹,聊勝于無吧垃帅。
處理iframe跨域的代碼示例(二)
這個方案我們需要增加一個跟頁面A同域的空白頁面作為跳板,例如:
http://a.example.com/empty.html
empty.html
頁面完全為空剪勿,不包含任何代碼也沒關(guān)系贸诚。做好這一步后,我們來重寫A厕吉、B兩個頁面的代碼:A頁面代碼:
<html>
<head>
<title>Example CORS for iframe</title>
</head>
<body>
<button>Post Message</button>
<p></p>
</body>
</html>
<script>
const msg = document.querySelector('p');
function openFrame(url, fn) {
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
var state = 0;
var cb = fn;
iframe.onload = function() {
if(state === 1) {
cb(iframe.contentWindow.name);
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else if(state === 0) {
state = 1;
setTimeout(() => {
iframe.contentWindow.location = 'empty.html';
},300)
}
};
iframe.src = url;
document.body.appendChild(iframe);
}
setInterval(() => {
var url = 'http://b.example.com/test.html';
openFrame(url, (data)=> {
message = data;
if(data && data.startsWith('ToParent:')) {
msg.innerHTML = 'Message from iframe:'+data.substring(9);
}
});
}, 1000);
</script>
- B頁面代碼:
<script>
var time = new Date().getTime().toString();
window.name=`ToParent:${time}`;
</script>
總結(jié)
- 以上兩種方法酱固,便是純前端實現(xiàn)的跨域通訊。
-
window.postMessage
是瀏覽器的標(biāo)準(zhǔn)API头朱,它實現(xiàn)的通訊非常實時运悲,而且代碼的實現(xiàn)方式也相當(dāng)優(yōu)雅,強(qiáng)烈推薦大家使用项钮。當(dāng)然班眯,使用的時候要注意瀏覽器兼容性,兼容舊版的瀏覽器比如IE烁巫,那么可以考慮用另外一個方案鳖敷。 - 利用
window.name
也確實能夠?qū)崿F(xiàn)在iframe跨域通訊的目標(biāo),盡管代碼看起來比較繞程拭。不過定踱,也確實是打了折扣,因為信息傳遞只能有單向的通道(A頁面獲取B頁面的信息)恃鞋。當(dāng)然崖媚,如果想實現(xiàn)A=>B傳遞信息亦歉,也有其他手段,譬如利用URL來傳遞參數(shù)畅哑‰瓤總的來說,該方案應(yīng)該屬于遠(yuǎn)古時代的產(chǎn)物荠呐,如果不考慮兼容舊的瀏覽器赛蔫,不推薦大家在項目上使用∧嗾牛可以把它放到代碼的博物館中呵恢,慢慢欣賞,就好比今天我們像看古人如何鉆木取火一般媚创。