iframe
利用iframe可以在當(dāng)前網(wǎng)頁(yè)中嵌入另一個(gè)完成的網(wǎng)頁(yè),類似小程序、iOS、android中的webview。
iframe同源
如果iframe嵌套的網(wǎng)頁(yè)不存在跨域問(wèn)題即是同源的,那么可以直接獲取iframe的dom進(jìn)行相應(yīng)的操作它相當(dāng)于一個(gè)普通的DOM節(jié)點(diǎn),他們的window是相同的(window.parent == window)。
需要注意的是iframe需要加載完成后才能獲取到它的window(iframe.contentWindow)和document(iframe.contentDocument)。
如果嵌入iframe的二級(jí)域名一致,也可實(shí)現(xiàn)同源策略。例如當(dāng)前網(wǎng)站域名test.a.com,iframe中嵌入的網(wǎng)站是prd.a.com,可以利用document.domain使瀏覽器忽略該差異,使得它們可以被作為“同源”的來(lái)對(duì)待,以便進(jìn)行跨窗口通信,那么在這個(gè)網(wǎng)站中都設(shè)置document.domain='a.com'相同的域之后就可達(dá)到同源的效果了。
iframe跨域通信
如果域名完全不同則可使用postMessage進(jìn)行相關(guān)通信操作。當(dāng)然要等到iframe加載完成使用它的contentWindow來(lái)進(jìn)行postMessage。
跨域是無(wú)法獲取iframe的document的,否則報(bào)錯(cuò)如下:

所以我們直接通過(guò)iframe的document來(lái)appendChild的方式注入腳本。只能通過(guò)發(fā)送消息。
<button
style={{height: 40}}
onClick={()=>{
webViewRef.current.sendMessage('SendMessageToJsPayResult',{a:'a',b:'n'});
}}>按鈕</button>
<WebView
ref={webViewRef}
onLoadFinsh={()=>{
if(webViewRef.current) {
webViewRef.current.injectScript(`alert('Hello, world!');`)
}
}}
onMessage={data => {
console.log('接收消息:',data);
}}
/>
webView的實(shí)現(xiàn)如下:
/**
* @description 自定義webview
* @example 其他h5項(xiàng)目發(fā)送消息舉例:
* if(window.parent && window.parent !== window) {
window.parent.postMessage(JSON.stringify({
type: 'GMember',
body: {
data: '中國(guó)嘻嘻'
}
}),'*')
}else {
console.log('此處不在iframe中')
}
* @example 其他web項(xiàng)目接收消息舉例:
window.addEventListener('message', (event)=> {
if(event.data && event.data.type == 'injectScript') {
eval(event.data.script);
}else if(event.data && event.data.type == 'GMember') {
console.log(event.data)
}
})
* @returns
<button
style={{height: 40}}
onClick={()=>{
webViewRef.current.sendMessage('SendMessageToJsPayResult',{a:'a',b:'n'});
}}>按鈕</button>
<WebView
ref={webViewRef}
onLoadFinsh={()=>{
if(webViewRef.current) {
webViewRef.current.injectScript(`alert('Hello, world!');`)
}
}}
onMessage={data => {
console.log('接收消息:',data);
}}
/>
*/
import React, {useEffect, useRef } from "react";
const WebView = React.forwardRef((props, ref) => {
const {
src="",
style={},
onMessage,
onLoadFinsh
} = props;
React.useImperativeHandle(ref, () => ({
// postMessage 防止沖突
sendMessage: (type,data)=>{
if(webRef.current) {
try {
getIframeContentWindow(webRef.current).postMessage({type,data}, '*')
} catch (error) {
}
}else {
throw new Error('無(wú)法獲取 iframe 的對(duì)象');
}
},
injectScript: (scriptString)=> {
try {
getIframeContentWindow(webRef.current).postMessage({
type: 'injectScript',
script: scriptString
}, '*')
} catch (error) {
}
},
// 同源策略
same_injectScript: (scriptString)=> {
try {
// 跨域時(shí)無(wú)法獲取到document,
if(window.location.origin == getIframeContentWindow(webRef.current).location.origin) {
const script = document.createElement('script');
script.innerHTML = scriptString;
// script.textContent = scriptString;
getIframeContentWindow(webRef.current).document.head.appendChild(script);
}
} catch (error) {
}
}
}));
const webRef = useRef(null);
useEffect(()=>{
const messageListener = (event)=> {
if (window == event.source) return;
if(event.data) {
const data = JSON.parse(event.data);
if(data.type == 'GMember') {
console.log(data)
}
onMessage && onMessage(data);
}
// 可以使用 event.source.postMessage(...) 向回發(fā)送消息
}
window.addEventListener('message', messageListener);
return ()=> {
window.removeEventListener('message', messageListener);
}
},[])
const getIframeContentWindow = (iframe)=> {
if (iframe.contentWindow) {
return iframe.contentWindow;
} else if (iframe.contentDocument && iframe.contentDocument.defaultView) {
return iframe.contentDocument.defaultView;
} else {
throw new Error('無(wú)法獲取 iframe 的內(nèi)容窗口');
}
}
return (
<div style={{display: 'flex',width: '100vw', height: '100vh',...style}}>
<iframe
style={{flex:1, border: 'none'}}
scrolling="no"
sandbox={'allow-forms allow-scripts'}
src={src}
ref={webRef}
onLoad={(e)=>{
onLoadFinsh && onLoadFinsh(e);
console.log('>>>>>>>>>>>>>>>>iframe 加載完成<<<<<<<<<<<<<<<<<')
}}
onError={err => {
console.log(err)
}}
/>
</div>
)
})
export {
WebView
}