How Code Sandboxes Avoid Flashing Iframe Updates

In a previous post we created a simple code sandbox from scratch using plain TypeScript and it works great but then I noticed something interesting.

Whenever you update code on sites such as Codepen the iframe flashes because it tears down and rebuilds the DOM (Document Object Model). When I was using the Svelte REPL, or CodeSandbox I noticed it doesnโ€™t do that โ€” in fact it updates instantly without the iframe flashing.

At first I wondered how could this be โ€” does it use some clever DOM updates behind the scenes? The answer is simpler than you might think.

The Flashing Iframe

First let me show you the offending code.

index.html
<textarea spellcheck="false"></textarea>
<iframe></iframe>
app.js
const editorElement = document.querySelector('textarea')
const iframeElement = document.querySelector('iframe')

editorElement.addEventListener('input', (event) => {
  // get current editor value
  const html = event.target.value

  // update iframe
  iframeElement.srcdoc = `
    <html>
      <body>
        ${html}
      </body>
    </html>
  `
})

The code just takes a <textarea> input and updates the srcdoc attribute of the <iframe> creating a minimal code sandbox.

In my example I have some basic styles that I havenโ€™t included here to keep things simple.

The Solution

The solution to the flashing iframe is to not update the srcdoc each time a change happens but send a message from the parent to perform DOM manipulations inside the <iframe> which causes no refresh.

We can do this using the window.postMessage method from the parent window and listen for messages inside the <iframe> using the message event listener.

app.js
const editorElement = document.querySelector('textarea')
const iframeElement = document.querySelector('iframe')

iframeElement.srcdoc = `
  <html>
    <head>
    <script type="module">
      window.addEventListener('message', (event) => {
        const { type, value } = event.data;

        if (type === 'html') {
          document.body.innerHTML = value;
        }
      })
    </script>
    </head>
    <body>
    </body>
  </html>
`

editorElement.addEventListener('input', (event) => {
  // get current editor value
  const value = event.target.value

  // imagine you had different file types
  const html = { type: 'html', value }

  iframeElement.contentWindow.postMessage(html, '*')
})

You can determine what to do based on the type. For example you can inject CSS, or insert a <script> tag to execute JavaScript code.

The second parameter of postMessage is the target origin. In this case weโ€™re saying * which means anything.

Conclusion

This might seem insignificant but it provides a magical user experience. Stay curious and question how things work.

Thanks for reading! ๐Ÿ„โ€โ™€๏ธ

Found a mistake?

Every post is a Markdown file so contributing is simple as following the link below and pressing the pencil icon inside GitHub to edit it.

Edit on GitHub
Subscribe For Updates