Usually front end is and background joint adjustment, or embedded webView client and client joint adjustment, front end and front end joint adjustment is what ghost? There are situations where another front end has written a large module (such as a game, an online IDE, a visual editing page, etc., which requires a sandbox environment) and the import needs to use iframe. In a large requirement, with a modular division of labor, it is obvious that one person is responsible for the functionality inside the iframe and another person is responsible for the main page. Different people are responsible for things at the same time display on the page interaction, so the two front-end development process is bound to have a joint adjustment process

Background: The parent page index. HTML contains an iframe, and the SRC of the iframe is the child page (a link to another HTML)

Traditional way – Iframe postMessage communication

// Parent page js
    document.querySelector("iframe").onload = (a)= > {
      window.frames[0].postMessage("data from parent"."*");
    };

// Child page js
    window.addEventListener(
      "message",
      e => {
        console.log(e); // e is the event object, and e.ata is the message sent by the parent page
      },
      false
    );
Copy the code

This is the traditional way. Note that when addEventListener receives a message, it must first verify the identity of the sender of the message using the Origin and source properties of the event object, which could result in cross-site scripting attacks. Postmessage can be used only after the onLoad of iframe is triggered

Iframe hash change communication

A means of low threshold that can cross domains

The parent page

    const iframe = document.querySelector("iframe");
    const { src } = iframe;
// Convert the data to a string, which is hashed to the child page
    function postMessageToIframe(data) {
      iframe.src = `${src}#The ${encodeURIComponent(JSON.stringify(data))}`;
    }
Copy the code

The child iframe page

    window.onhashchange = e= > {
// Listen for hash changes, serialize JSON
      const data = JSON.parse(decodeURIComponent(location.hash.slice(1)));
      console.log(data, "data >>>>");
    };
Copy the code

Open the parent page and execute postMessageToIframe({TXT: ‘I am lhyt’})

In turn, the child page communicates with the parent page using parent:

/ / child pages
parent.postMessageToIframe({ name: "from child" })

// Parent page, the code is the same as the child page
    window.onhashchange = (a)= > {
      const data = JSON.parse(decodeURIComponent(location.hash.slice(1)));
      console.log(data, "data from child >>>>");
    };
Copy the code

Note:

  • Parent to child hash communication, there is no threshold, can be cross-domain, can be directly double-click open HTML
  • Cross-domain errors occur when child pages use parentUncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.

Onstorage event

Parent and child iframe page communication

Localstorage is the storage space shared by the browser’s domain tags. Html5 supports an onstorage event, and we can listen for changes by adding a listener on the window object: window.adDeventListener (‘storage’, (e) => console.log(e))

Note that this event is triggered only when the current page changes localStorage, and the current page changes localStorage will not trigger listener function!!

/ / the parent page
    setTimeout((a)= > {
      localStorage.setItem("a", localStorage.getItem("a") + 1);
    }, 2000);
    window.addEventListener("storage", e => console.log(e, "parent"));

/ / child pages
window.addEventListener("storage", e => console.log(e, "child"));
Copy the code

The printed storageEvent looks like this:

More SAO operation, and their own communication

Two pages, two pieces of HTML, is there a way to write one HTML instead of two HTML? It can!

Add a query or hash to the URL to indicate that the page is a child page. If it is the parent page, create an iframe. SRC is the href of the page plus the query parameter. The parent page HTML doesn’t need any other tags, just a script, okay

    const isIframe = location.search;
    if (isIframe) {
/ / child pages
      window.addEventListener("storage", e => console.log(e, "child"));
    } else {
// Create an iframe
      const iframe = document.createElement("iframe");
      iframe.src = location.href + "? a=1";
      document.body.appendChild(iframe);
      setTimeout((a)= > {
        localStorage.setItem("a", localStorage.getItem("a") + 1);
      }, 2000);
      window.addEventListener("storage", e => console.log(e, "parent"));
    }
Copy the code

MessageChannel

MessageChannel creates a new MessageChannel and sends data through its two MessagePort properties, and is available in Web workers. An instance of MessageChannel has two properties, portL1 and port2. Send a message to port1, and port2 will receive it.

/ / the parent page
    const channel = new MessageChannel();
// Inject port2 into the window of the child page
    iframe.contentWindow.port2 = channel.port2;
    iframeonload = (a)= > {
      // The parent page uses port1 to send a message, and port2 receives it
      channel.port1.postMessage({ a: 1 });
    };

// Child page, using port2 injected by the parent page
    window.port2.onmessage = e= > {
      console.error(e);
    };
Copy the code

Advantages of MessageChannel: Objects can be passed without manual serialization or deserialization, and the other port receives a deep copy of the object

SharedWorker

Is a type of worker, which can be used by multiple pages at the same time and accessed from several browsing contexts, such as several Windows, iframe and worker. It has a different global scope – only part of the methods under normal Winodow. Let multiple pages share a worker and use the worker as the medium to realize communication

The worker of the code

// Store all connection ports
const everyPorts = [];
onconnect = function({ ports }) {
// When onconnect is triggered, it is stored in an arrayeveryPorts.push(... ports);// Add the listener message event to all ports each time you connect
  [...ports].forEach(port= > {
    port.onmessage = function(event) {
// Every time a message is received, broadcast to all connected ports except the one that sent the message
      everyPorts.forEach(singlePort= > {
        if (port !== singlePort) {
          singlePort.postMessage(event.data.data);
        }
      });
    };
  });
};
Copy the code

Parent page JS code

    const worker = new SharedWorker("./worker.js");
    window.worker = worker;
    worker.port.addEventListener(
      "message",
      e => {
        console.log("parent:", e);
      },
      false
    );
    worker.port.start();
    setTimeout((a)= > {
      worker.port.postMessage({
        from: "parent".data: {
          a: 111.b: 26}}); },2000);
Copy the code

Iframe subpage js code:

    const worker = new SharedWorker("./worker.js");
    worker.port.onmessage = function(e) {
      console.log("child", e);
    };
    worker.port.start();
    setTimeout((a)= > {
      worker.port.postMessage({ data: [1.2.3]}); },1000);
Copy the code

In normal cases, postMessage should occur when the onload is complete, otherwise the onMessage is not received before the load is complete and the event is not bound

SharedWorker can also pass objects

Inject objects and methods directly

Many of the examples above use contentWindow, and since contentWindow is the iframe’s own window, we can inject anything we want for iframe to call. The injection function is one of the common methods used for front-end and client integration. The child page calls the parent page’s methods, and because of the parent global property, the parent page’s window is also available

/ / the parent page
    document.querySelector("iframe").contentWindow.componentDidMount = (a)= > {
      console.log("iframe did mount");
    };

/ / child pages
    window.onload = (a)= > {
      // Suppose there is a sequence of processes running like react...
      setTimeout((a)= > {
        // suppose the react component didmount
        window.componentDidMount && window.componentDidMount();
      }, 1000);
    };
Copy the code

Next, based on the window injection method to iframe, to design a simple communication module

  • Parent page active tune page, child page by the parent page tune
  • Parent page quilt page tune, child page tune parent page

Under the parent page, attach the parentPageApis object to the window, which is a collection of methods called by the child page. And inject a callParentApi method into the child page to call the parent page’s method.

   const iframe = document.querySelector("iframe");

    window.parentPageApis = window.parentPageApis || {};
    // The parent page injects itself with methods called by the child page
    Object.assign(window.parentPageApis, {
      childComponentDidMount() {
        console.log("Child page did mount");
      },
      changeTitle(title) {
        document.title = title;
      },
      showDialog() {
        / / window}});// Inject a callParentApi method into the child page to call the parent page
    iframe.contentWindow.callParentApi = function(name, ... args) {
      window.parentPageApis[name] && window.parentPageApis[name].apply(null, args);
    };

    iframe.contentWindow.childPageApis =
      iframe.contentWindow.childPageApis || {};
    Object.assign(iframe.contentWindow.childPageApis, {
      // The parent page can also inject methods into the child page
    });
    setTimeout((a)= > {
// Call the methods of the child page
      callChildApi("log"."The log method of the parent page tune page prints.");
    }, 2000);
Copy the code

The child page also injects the callChildApi method into the parent page and writes its own set of external methods on the childPageApis

    window.childPageApis = window.childPageApis || {};
    Object.assign(window.childPageApis, {
      // The child page injects its own methodslog(... args) {console.log(...args);
      }
    });
    window.parent.window.callChildApi = function(name, ... args) {
      window.childPageApis[name] && window.childPageApis[name].apply(null, args);
    };
    window.onload = (a)= > {
      // Suppose there is a sequence of processes running like react...
      setTimeout((a)= > {
        // if the react component didmount, tell the parent page
        window.callParentApi("childComponentDidMount");
      }, 1000);
    };
Copy the code

The last

The above storage, SharedWorker scheme is also applicable to “different TAB communication” this problem. In general, SharedWorker is safe, global injection method is flexible, and hash transformation communication is simple. Postmessage, hash changes, and storage events are all based on strings, while MessageChannel and SharedWorker can pass any “copyable value”. Global injection can do whatever it wants, but it is the most dangerous and needs to be guarded against

Pay attention to the public account “Different front-end”, learn front-end from a different perspective, grow fast, play with the latest technology, explore all kinds of black technology