现在做的项目中,好多使用iframe的场景。
封装了一下iframe之间的通信。
核心代码iframeUtil.ts
type DispatcherItem = {type: string, callback: Function}; type IKeyValueMap<T> = {[key: string]: T}; type IframeMsgEvent = {type: string, id: string, data: Object}; type WindowMsgEvent = {type: string, data: IframeMsgEvent}; type MethodEvent = {name: string, id: string, params: Object[]}; class Dispatcher{ private arr: DispatcherItem[] = []; constructor(){ } public emit(type: string, data: Object): void { for(let i: number = 0; i < this.arr.length; i++) { if(this.arr[i].type == type) { this.arr[i].callback(data); } } } public on(type: string, callback: Function): void { if(this.indexOf(type, callback) == -1) { this.arr.push({ type: type, callback: callback }); } } public off(type: string, callback: Function): void { const ind: number = this.indexOf(type, callback); if(ind != -1) { this.arr.splice(ind, 1); } } public onece(type: string, callback: Function): void { const c: Function = (data) => { callback(data); this.off(type, callback); }; this.on(type, c); } private indexOf(type: string, callback: Function): number { for(let i: number = 0; i < this.arr.length; i++) { if(this.arr[i].type == type && this.arr[i].callback == callback) { return i; } } return -1; } } class IframeBase { protected id: string; protected dp: Dispatcher = new Dispatcher(); protected methods: IKeyValueMap<Function> = {}; constructor() { // @ts-ignore window.addEventListener('message', this.onMessage); } public emit(type: string, data: Object): void { } public on(type: string, callback: Function): void { this.dp.on(type, callback); } public off(type: string, callback: Function): void { this.dp.off(type, callback); } public call(name: string, callback: Function, ...params: Object[]): void{ this.method('call', name, callback, params); } // @ts-ignore public async asyncCall(name: string, ...params: Object[]): Promise<Object> { // @ts-ignore return this.asyncMethod('call', name, ...params); } protected method(type: string, name: string, callback: Function, ...params: Object[]): void { const id = this.getUid(); this.emit(type, { name: name, id: id, params: params }); this.dp.onece(id, callback); } // @ts-ignore protected async asyncMethod(type: string, name: string, ...params: Object[]): Promise<Object> { // @ts-ignore return new Promise((resolve, reject)=>{ this.method(type, name, resolve, ...params); }); } protected callMethod(o: MethodEvent) { if(this.methods[o.name]) { const f = this.methods[o.name]; if(typeof f == 'function') { const r: Object = this.methods[o.name](...o.params); this.emit(o.id, r); } else { const r: Object = this.methods[o.name]; this.emit(o.id, r); } } }; protected onMessage = (msg: WindowMsgEvent) => { const data: IframeMsgEvent = msg.data; if(data.id == this.id) { if(data.type == 'call') { this.callMethod(data.data as MethodEvent); } else { this.dp.emit(data.type, data.data); } } }; /** * 唯一id * @return {string} */ protected getUid(): string { return Math.random().toString(16).substr(2); } /** * 获取get方式传递的参数 * @param s * @return {IKeyValueMap<string>} */ protected getPar(s = undefined): IKeyValueMap<string> { const url: string = s || location.search; //获取url中"?"符后的字串 const result: IKeyValueMap<string> = {}; if (url.indexOf("?") != -1) { const str: string = url.substr(1); const arr: string[] = str.split("&"); for(let i = 0; i < arr.length; i++) { result[arr[i].split("=")[0]]=decodeURI(arr[i].split("=")[1]); } } return result; } } class IframeParent extends IframeBase { constructor() { super(); const par = this.getPar(); this.id = par.ifrId; } public emit(type: string, data: Object){ if(window != window.parent) { window.parent.postMessage({ type: type, id: this.id, data: data }, '*'); } } } class IframeChild extends IframeBase { protected iframe: HTMLIFrameElement; constructor(iframe: HTMLIFrameElement) { super(); this.iframe = iframe; const src: string = iframe.src; const par: IKeyValueMap<string> = this.getPar(src.substr(src.indexOf('?'))); this.id = par.ifrId; } public emit(type: string, data: Object): void { if(this.iframe) { this.iframe.contentWindow.postMessage({ type: type, id: this.id, data: data }, '*'); } } }
使用方式parent.html
<meta charset="utf-8"/> <iframe id="iframe1" src="./child1.html?ifrId=1"></iframe> <iframe id="iframe2" src="./child2.html?ifrId=2"></iframe> <script type="text/javascript" src="./iframeUtil.js"></script> <script type="text/javascript"> var iframe1 = document.getElementById("iframe1"); var iframe2 = document.getElementById("iframe2"); var child1 = new IframeChild(iframe1); var child2 = new IframeChild(iframe2); function func1(data){ console.log('load1', data); } child1.on('load', func1); function func2(data){ console.log('load2', data); } child2.on('load', func2); setTimeout(async function(){ console.time('a'); for(var i = 0; i < 1; i++) { var c = await child1.asyncCall('add', 1, 2); console.log('1 + 2 = ' + c); } console.timeEnd('a'); const p = await child1.asyncCall('position'); console.log(p); }, 100); child1.methods.sub = child2.methods.sub = function(a, b){ return a - b; } child1.methods.position = child2.methods.position = {x: 100, y: 200}; </script>
child1.html
<meta charset="utf-8"/> <h1>child 1</h1> <script type="text/javascript" src="./iframeUtil.js"></script> <script type="text/javascript"> var p = new IframeParent(); p.emit('load', 'hello child1'); p.methods.add = function(a, b){ return a + b; } p.methods.position = {x: 1, y: 2}; setTimeout(async function(){ var c = await p.asyncCall('sub', 5, 2); console.log('5 - 2 = ' + c); var c = await p.asyncCall('position'); console.log('child1:', c); }, 0); </script>
child2.html
<meta charset="utf-8"/> <h1>child 2</h1> <script type="text/javascript" src="./iframeUtil.js"></script> <script type="text/javascript"> var p = new IframeParent(); p.emit('load', 'hello child2'); p.methods.add = function(a, b){ return a + b; } p.methods.position = {x: 1, y: 2}; setTimeout(async function(){ var c = await p.asyncCall('sub', 8, 5); console.log('8 - 5 = ' + c); var c = await p.asyncCall('position'); console.log('child2:', c); }, 0); </script>
可以下载最后的zip包,查看效果。
可以看到上面的代码,使用比较简单。
父html,首先引入iframeUtil.js,iframe地址添加一个ifrId,用于区分不同的iframe,然后用iframe元素创建一个IframeChild实例,创建好了之后,可以给IframeChild实例的methods添加属性或者方法,添加之后,就可以在子iframe中调用。可以通过IframeChild实例的asyncCall调用子iframe中的属性或方法。
子html,同上,只是创建的是IframeParent实例,不用传参数。
有编译好的iframeUtil.js文件,也有直接用js写的。iframeUtil002.js,用class。iframeUtil001.js,没用class,自己写原型链。由于一些历史遗留原因,写了3个版本。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。