DEV Community

Giles Dring
Giles Dring

Posted on

Making iframes work better

The humble <iframe> is a useful way of presenting content from another web page, but can be quite difficult to make work seamlessly. This short article presents a way of making them a little easier to integrate into a responsive design.

Setting up an iframe

Adding some nested content is as simple as adding an <iframe> tag with a src attribute pointing at the content to include.

<iframe src="/nested-content.html"></iframe>
Enter fullscreen mode Exit fullscreen mode

Out of the box, this leaves a little to be desired - notably the height and width take no notice of the size of the host page or the nested content!

A browser screenshot showing a mid-grey web page entitled "Main Page" with another page embedded using an iframe. The embedded page is entitled "Nested Page". The nested page is lighter grey in colour, and is narrower than the main page, and has quite a bit of whitespace at the bottom

The width is fairly easily fixed by setting the width of the iframe element to 100%. The quickest way to do this is with a default size.

iframe { width: 100%; }
Enter fullscreen mode Exit fullscreen mode

How tall is this iframe?

The height is a slightly different matter. While it is possible to set height the same way (or via an attribute on the iframe), there's the question of how large that should be. Ideally we'd set this to the height of the iframe content, but there's no foolproof way of knowing this. For a simple nested page it's fairly easy.

#nested-content { height: 5.5rem; }
Enter fullscreen mode Exit fullscreen mode

A browser screenshot showing a mid-grey web page entitled "Main Page" with another page embedded using an iframe. The embedded page is entitled "Nested Page". The nested page is lighter grey in colour, and sized to fit the small amount of content

This looks much better! There's a problem though: this is a fixed height, so the content will not respond to changes in screen size or content in the nested page. If the content is longer than the size we set, we end up with a scroll bar in the iframe, which may not be what we want.

Making the height responsive

The good news is, we can achieve this dynamic resizing using a trivial amount of JavaScript! Because of limitations introduced by CORS (Cross-Origin Resource Sharing) to prevent attacks, we have to use the postMessage() method to safely share information between the parent and nested page.

The basic flow is to calculate the size of the nested page using the getBoundingClientRect() method of the top-level element of the nested page. This is accessible using document.documentElement. We then message the parent page with the new height. An event listener makes this reactive to changes in height of the page.

function sendResizeMessage() {
  const height = document.documentElement
    .getBoundingClientRect().height;
  parent.postMessage({ height }, parent.location);
}
sendResizeMessage();
addEventListener('resize', sendResizeMessage);
Enter fullscreen mode Exit fullscreen mode

All we need to do now is listen for this message in the parent page, extract the height and set the iframe size accordingly.

const iframe = document.getElementById('iframe-id');
addEventListener('message', (event) => {
  const height = event.data.height;
  iframe.style.height = height + 'px';
});
Enter fullscreen mode Exit fullscreen mode

Like good citizens, these are both loaded in DOMContentLoaded handlers.

addEventListener(
  'DOMContentLoaded',
  <initialisation code here!>
);
Enter fullscreen mode Exit fullscreen mode

Making it a bit more secure

Although the code we're running is fairly harmless, there is a possibility that someone could message us and set the height of the iframe. The postMessage() documentation suggests confirming the sender's identity by checking the event source and origin.

The updated message listener now looks like this:

const iframe = document.getElementById('iframe-id');
addEventListener('message', (event) => {
  if(!(
    event.source === iframe.contentWindow &&
    event.origin === 'https://example.com'
  )) return;

  const height = event.data.height;
  iframe.style.height = height + 'px';
});
Enter fullscreen mode Exit fullscreen mode

All we've done here is to test that

  • the event source is the iframe content window
  • the event origin is the expected url

Note that this is negated so if it doesn't match, the function returns early.

I've got a slightly more fully worked out example in the iframe-resizer repo on GitHub, and you can see it in action.

I hope this has been helpful, and thanks for reading!

Top comments (0)