动态创建具有给定 HTML 的 iframe

我尝试用 JavaScript 创建一个 iframe,然后用任意的 HTML 填充它,如下所示:

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

我希望 iframe包含一个有效的窗口和文档,但事实并非如此:

> console. log (iframe.contentWindow) ;
无效

自己试试看: http://jsfiddle.net/TrevorBurnham/9k9Pe/

我忽略了什么?

287652 次浏览

在 javascript 中设置新创建的 iframesrc并不会触发 HTML 解析器,直到元素插入到文档中。然后更新 HTML,调用 HTML 解析器并按预期处理该属性。

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);

这也是对你问题的回答,值得注意的是,这种方法与一些浏览器存在兼容性问题,请参阅@mschr 的跨浏览器解决方案的答案。

虽然你的 src = encodeURI应该可以工作,但我会选择不同的方式:

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();

由于这没有 x 域限制,完全是通过 iframe句柄完成的,因此稍后您可以访问和操作框架的内容。所有您需要确保的是,内容已经呈现,这将(取决于浏览器类型)在。发出 write 命令-在调用 close()时不一定执行 但是

100% 兼容的回调方式可以是这种方法:

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

然而,IFrame 拥有 onload 事件:

iframe.onload = function() {
var div=iframe.contentWindow.document.getElementById('mydiv');
};

还有一种替代方法可以创建内容为 HTML 字符串的 iframe: Srcdoc 属性。这在较老的浏览器中不受支持(其中最主要的是: Internet Explorer,可能还有 Safari?),但是对于这种行为有一个 填料,您可以将其放入 IE 的条件注释中,或者使用类似 has.js 的东西来有条件地延迟加载它。

谢谢你的好问题,这个问题已经困扰我好几次了。在使用 dataURI HTML 源代码时,我发现必须定义一个完整的 HTML 文档。

下面是一个修改过的示例。

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

注意用 <html>标记和 iframe.src字符串包装的 html 内容。

需要将 iframe 元素添加到要解析的 DOM 树中。

document.body.appendChild(iframe);

除非在浏览器上使用 disable-web-security,否则无法检查 iframe.contentDocument。 你会收到留言的

DOMException: 未能从‘ HTMLIFrameElement’中读取‘ contentDocument’属性: 阻止原点为“ http://localhost:7357”的帧访问跨原点的帧。

这样做

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


var frame_win = getIframeWindow(el);


console.log(frame_win);
...

这里定义了 getIframeWindow

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;
}

我知道这是一个老问题,但我想我会提供一个例子使用 srcdoc属性,因为这是现在的 得到广泛支持,这是问题是经常查看。

使用 srcdoc属性,您可以提供嵌入的内联 HTML。如果支持,它将覆盖 src属性。如果不受支持,浏览器将返回到 src属性。

我还建议使用 sandbox属性对框架中的内容应用额外的限制。如果 HTML 不是您自己的,这一点尤其重要。

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

如果需要支持较老的浏览器,可以检查对 srcdoc的支持,并从其他答案回退到其他方法之一。

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);

URL 方法只适用于小的 HTML 片段。更可靠的方法是从 blob 生成一个对象 URL,并将其用作动态 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);

(函数(){

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;

})();