27
2022
09

iframe通信

现在做的项目中,好多使用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个版本。


源码打包下载

« 上一篇下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。