203

I'm trying to create an iframe from JavaScript and fill it with arbitrary HTML, like so:

var html = '<body>Foo</body>';
var iframe = document.createElement('iframe');
iframe.src = 'data:text/html;charset=utf-8,' + encodeURI(html);

I would expect iframe to then contain a valid window and document. However, this isn't the case:

> console.log(iframe.contentWindow);
null

Try it for yourself: http://jsfiddle.net/TrevorBurnham/9k9Pe/

What am I overlooking?

2

9 Answers 9

292

Allthough your src = encodeURI should work, I would have gone a different way:

var iframe = document.createElement('iframe');
var html = '<body>Foo</body>';
document.body.appendChild(iframe);
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(html);
iframe.contentWindow.document.close();

As this has no x-domain restraints and is completely done via the iframe handle, you may access and manipulate the contents of the frame later on. All you need to make sure of is, that the contents have been rendered, which will (depending on browser type) start during/after the .write command is issued - but not nescessarily done when close() is called.

A 100% compatible way of doing a callback could be this approach:

<html><body onload="parent.myCallbackFunc(this.window)"></body></html>

Iframes has the onload event, however. Here is an approach to access the inner html as DOM (js):

iframe.onload = function() {
   var div=iframe.contentWindow.document.getElementById('mydiv');
};
Sign up to request clarification or add additional context in comments.

20 Comments

Interesting; I hadn't seen this technique before. I know that the URI encoding/decoding adds a performance hit, yet I've also seen it described as the sole alternative in environments that don't support the srcdoc property. Is there a downside to the document.write approach?
@mschr Does this method support full HTML page code where includes and stylesheets are loaded as well? See stackoverflow.com/questions/19871886
Basically, yes i believe it should, ill comment there. The technique basically opens an inputtextstream which you may write to via document.write. In turn, a normal loading webpage retreives this through a websocket-stream.
This works in Internet Explorer! That is handy since data URIs cannot be used as iframe sources in any version of IE. caniuse.com/#feat=datauri
Interesting that the document.body.appendChild(iframe) is required for this to work.
|
153

Setting the src of a newly created iframe in javascript does not trigger the HTML parser until the element is inserted into the document. The HTML is then updated and the HTML parser will be invoked and process the attribute as expected.

http://jsfiddle.net/9k9Pe/2/

var iframe = document.createElement('iframe');
var html = '<body>Foo</body>';
iframe.src = 'data:text/html;charset=utf-8,' + encodeURI(html);
document.body.appendChild(iframe);
console.log('iframe.contentWindow =', iframe.contentWindow);

Also this answer your question it's important to note that this approach has compatibility issues with some browsers, please see the answer of @mschr for a cross-browser solution.

3 Comments

Doesn't even work in IE10. @mschr's answer works in IE7+ for sure, maybe even older versions.
His question was "What am I overlooking?" and that was the fact that is iframe wasn't appended to the document. I never claim it was cross browsers and it's only over a year after I answered that someone actually complained. No it's not cross browser. But let's be honest if you want code quality there is probably a much cleaner solution than using a iframe in the first place :)
Note that encodeURI(...) doesn't encode all characters, so if your HTML has special characters like #, this will break. encodeURIComponent(...) encodes these characters. See this answer
29

I know this is an old question but I thought I would provide an example using the srcdoc attribute as this is now widely supported and this is question is viewed often.

Using the srcdoc attribute, you can provide inline HTML to embed. It overrides the src attribute if supported. The browser will fall back to the src attribute if unsupported.

I would also recommend using the sandbox attribute to apply extra restrictions to the content in the frame. This is especially important if the HTML is not your own.

const iframe = document.createElement('iframe');
const html = '<body>Foo</body>';
iframe.srcdoc = html;
iframe.sandbox = '';
document.body.appendChild(iframe);

If you need to support older browsers, you can check for srcdoc support and fallback to one of the other methods from other answers.

function setIframeHTML(iframe, html) {
  if (typeof iframe.srcdoc !== 'undefined') {
    iframe.srcdoc = html;
  } else {
    iframe.sandbox = 'allow-same-origin';
    iframe.contentWindow.document.open();
    iframe.contentWindow.document.write(html);
    iframe.contentWindow.document.close();
  }
}

var iframe = document.createElement('iframe');
iframe.sandbox = '';
var html = '<body>Foo</body>';

document.body.appendChild(iframe);
setIframeHTML(iframe, html);

2 Comments

But what if a new element does not need to be created? If an iframe already exists and the data is just to be updated?
This use can use postMessage to send a message to the JavaScript within the iframe describing what changes need to happen, then the iframe can update itself with its own code. Bit tricky, but I think it is the only way.
17

Thanks for your great question, this has caught me out a few times. When using dataURI HTML source, I find that I have to define a complete HTML document.

See below a modified example.

var html = '<html><head></head><body>Foo</body></html>';
var iframe = document.createElement('iframe');
iframe.src = 'data:text/html;charset=utf-8,' + encodeURI(html);

take note of the html content wrapped with <html> tags and the iframe.src string.

The iframe element needs to be added to the DOM tree to be parsed.

document.body.appendChild(iframe);

You will not be able to inspect the iframe.contentDocument unless you disable-web-security on your browser. You'll get a message

DOMException: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "http://localhost:7357" from accessing a cross-origin frame.

Comments

17

There is an alternative for creating an iframe whose contents are a string of HTML: the srcdoc attribute. This is not supported in older browsers (chief among them: Internet Explorer, and possibly Safari?), but there is a polyfill for this behavior, which you could put in conditional comments for IE, or use something like has.js to conditionally lazy load it.

1 Comment

support for this is pretty mainstream now (save IE). And this is definitely preferable to accessing contentDocument directly - especially since if used in conjunction with the sandbox attribute, you can't access contentDocument.
10

The URL approach will only work for small HTML fragements. The more solid approach is to generate an object URL from a blob and use it as a source of the dynamic iframe.

const html = '<html>...</html>';
const iframe = document.createElement('iframe');
const blob = new Blob([html], {type: 'text/html'});
iframe.src = window.URL.createObjectURL(blob);
document.body.appendChild(iframe);

1 Comment

This approach has a problem that the frame's window.location.href will be something like "blob:example.com/83f3ac60-2cd9-42dd-b430-e1c09484009e"
1

Yet another way to do this:

  // 1. Create elements with regular strings
  const myIframeContent = `<h1>Sup</h1>`;
  
  // 2. Set the `srcdoc` aka HTML of the iframe
  const myNewIframe = `<iframe id="myIframe" srcdoc="${myIframeContent}"></iframe>`;
  
  // 3. Magically turn those strings in to HTML by appending the string to the document.body
  document.body.insertAdjacentHTML('beforeend', myNewIframe);
  
  // 4. Look at all the cools things you have now!
  console.log('[myIframe.contentWindow]', myIframe.contentWindow);

Docs: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe

Comments

0

Do this

...
var el = document.getElementById('targetFrame');

var frame_win = getIframeWindow(el);

console.log(frame_win);
...

getIframeWindow is defined here

function getIframeWindow(iframe_object) {
  var doc;

  if (iframe_object.contentWindow) {
    return iframe_object.contentWindow;
  }

  if (iframe_object.window) {
    return iframe_object.window;
  } 

  if (!doc && iframe_object.contentDocument) {
    doc = iframe_object.contentDocument;
  } 

  if (!doc && iframe_object.document) {
    doc = iframe_object.document;
  }

  if (doc && doc.defaultView) {
   return doc.defaultView;
  }

  if (doc && doc.parentWindow) {
    return doc.parentWindow;
  }

  return undefined;
}

Comments

-3

(function(){

var frame = document.createElement('iframe');
frame.src = 'https://1zr2h9xgfxqt.statuspage.io/embed/frame';
frame.style.position = 'fixed';
frame.style.border = 'none';
frame.style.boxShadow = '0 20px 32px -8px rgba(9,20,66,0.25)';
frame.style.zIndex = '9999';
frame.style.transition = 'left 1s ease, bottom 1s ease, right 1s ease';

var mobile;
if (mobile = screen.width < 450) {
  frame.src += '?mobile=true';
  frame.style.height = '20vh';
  frame.style.width = '100vw';
  frame.style.left = '-9999px';
  frame.style.bottom = '-9999px';
  frame.style.transition = 'bottom 1s ease';
} else {
  frame.style.height = '115px';
  frame.style.width = '320px';
  frame.style.left = 'auto';
  frame.style.right = '-9999px';
  frame.style.bottom = '60px';
}

document.body.appendChild(frame);

var actions = {
  showFrame: function() {
    if (mobile) {
      frame.style.left = '0';
      frame.style.bottom = '0';
    }
    else {
      frame.style.left = 'auto';
      frame.style.right = '60px'
    }
  },
  dismissFrame: function(){
    frame.style.left = '-9999px';
  }
}

window.addEventListener('message', function(event){
  if (event.data.action && actions.hasOwnProperty(event.data.action)) {
    actions[event.data.action](event.data);
  }
}, false);

window.statusEmbedTest = actions.showFrame;

})();

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.