Making iframes work better
Tags: javascript, html
Published: 2022-05-05 20:04:47

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.

```html
<iframe src="/nested-content.html"></iframe>
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wh4y3ms7eydnp68cqm05.png)

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.

```css
iframe { width: 100%; }
```

## 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.

```css
#nested-content { height: 5.5rem; }
```

![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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9d0b20trmcuutijc31l.png)

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)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) to prevent attacks, we have to use the [`postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/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()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/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.
 
```js
function sendResizeMessage() {
  const height = document.documentElement
    .getBoundingClientRect().height;
  parent.postMessage({ height }, parent.location);
}
sendResizeMessage();
addEventListener('resize', sendResizeMessage);
```

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

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

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

```js
addEventListener(
  'DOMContentLoaded',
  <initialisation code here!>
);
```

## 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:

```js
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';
});
```

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](https://github.com/dringtech/iframe-resizer), and [you can see it in action](https://dringtech.com/iframe-resizer/).

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