Two-way communication between an iframe and its parent page
Iframes get a bad wrap for everything from security problems, to usability and SEO issues. Despite this, they are one of the tools at our disposal, and knowing how to use them effectively could open the door to new solutions to old problems. Being able to send data between the iframe and the parent page is a useful trick for delivering more integrated solutions, rather than the traditional boring "page-in-a-page" way iframes get used.
The method examined here isn't just for iframes though, it will work in any case where you have access to another page's window
object (so popups, and embedded web-browsers can join in on the fun too). Iframes are easy to play with though, so we will use them for this example.
Establishing 2-way communication between a child page and its parent can be done a few ways, but generally, the recommended approach is to use window.postMessage
, if the technologies you are using support it (sorry IE users).
So let's create a basic page to act as our parent.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Parent Page</title>
</head>
<body>
<h2>Container</h2>
<textarea id="output" cols="30" rows="10" disabled>awaiting data...</textarea>
<div>
<input type="text" id="field" value="type something fun here" />
<button id="send">Send</button>
</div>
<div>
<iframe
height="500px"
id="inner"
src=""
frameborder="0"
></iframe>
</div>
</body>
</html>
Here we have a very basic page. I will use a disabled textarea for showing data from incoming messages from the embedded page, and an input and button for sending messages to said page. Aside from that, we have the guest of honor, the iframe
itself, currently with no source for it, because we need to make a page to embed first. Let's do that.
The Embedded Page
This is the page which will be running inside the iframe or popup. Structurally we will make something very similar to the parent page.
<!-- inner.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Embedded Page</title>
</head>
<body>
<h2>Embedded</h2>
<textarea cols="30" rows="10" disabled id="output">awaiting data...</textarea>
<input type="text" id="field" />
<button id="send">Send</button>
</body>
</html>
Now we need to load this page into the iframe. The easiest way to do this is with something like the npm package serve
. If you have npm
installed, navigate to the directory where these files are located, and run npx serve
.
You should get some output indicating which port the assets are being served on. In my case, they are being served on port 5000. When I visit http://localhost:5000 I will be served the parent page, index.html, and if I visit http://localhost:5000/inner.html, I will get the page I want to embed.
This means I can use that URL as the source for my iframe, so I will set that as the value of my src
attribute now.
<iframe
height="500px"
id="inner"
src="http://localhost:5000/inner.html"
frameborder="1"
></iframe>
With that out of the way, if we visit the index.html page we should now see the iframe containing our other page below it. The next step is to write some JavaScript to facilitate communication between the two pages.
The JavaScript
Firstly, we need to write some code on the index page which will wait for the iframe to load, and the get the reference to its window
object so that we can call the window.postMessage
function on it.
We will also need to add an event listener to send a message when we click our send button, and an event listener to display any messages we receive from the embedded page.
In index.html, create a <script>
tag before the end of your </body>
tag which contains something like the following snippet.
<script>
// assign variables with references to the DOM nodes we will be interacting with
const output = document.getElementById("output");
const iframe = document.getElementById("inner");
const button = document.getElementById("send");
const field = document.getElementById("field");
// we will assign this value once the iframe is ready
let iWindow = null;
// This event listener will run when we click the send button
button.addEventListener("click", () => {
// don't do anything if the iframe isn't ready yet
if (iWindow === null) {
return;
}
// otherwise, get the value of our text input
const text = field.value;
// and send it to the embedded page
iWindow.postMessage(text);
});
// This event listener will run when the embedded page sends us a message
window.addEventListener("message", (event) => {
// extract the data from the message event
const { data } = event;
// display it in our textarea as formatted JSON
output.value = JSON.stringify(data, null, 2);
});
// Once the iframe is done loading, assign its window object to the variable we prepared earlier
iframe.addEventListener("load", () => {
iWindow = iframe.contentWindow;
});
</script>
That JavaScript alone won't do much though, we need to add the corresponding code in the embedded page, so add another script tag in inner.html containing the following
<script>
// set up references to DOM nodes
const output = document.getElementById("output");
const button = document.getElementById("send");
const field = document.getElementById("field");
// create a variable for the parent window. We will assign it once we get the first message.
let parent = null;
// add an event listener to send messages when the button is clicked
button.addEventListener("click", () => {
// don't do anything if there is no parent reference yet
if (parent === null) {
return;
}
// otherwise get the field text, and send it to the parent
const text = field.value;
parent.postMessage(text);
});
// add an event listener to run when a message is received
window.addEventListener("message", ({ data, source }) => {
// if we don't have a reference to the parent window yet, set it now
if (parent === null) {
parent = source;
}
// now we can do whatever we want with the message data.
// in this case, displaying it, and then sending it back
// wrapped in an object
output.textContent = JSON.stringify(data);
const response = {
success: true,
request: { data },
};
parent.postMessage(response);
});
</script>
Save all your files, refresh your browser, and give it a try.
Using this method of sending messages opens up a whole new level of interactivity when it comes to popups, iframes, and embedded browsers, paving the way for some pretty cool interactions if you play your cards right.
Pastebin links to source:
- index.html - https://pastebin.com/A0HFp6c5
- inner.html - https://pastebin.com/ba6TNXK5