Crear un iframe con HTML dado dinámicamente

148

Estoy tratando de crear un iframe desde JavaScript y llenarlo con HTML arbitrario, así:

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

Esperaría iframecontener una ventana y un documento válidos. Sin embargo, este no es el caso:

> console.log (iframe.contentWindow);
nulo

Pruébelo usted mismo: http://jsfiddle.net/TrevorBurnham/9k9Pe/

¿Qué estoy pasando por alto?

Trevor Burnham
fuente
9
Tenga en cuenta que HTML5 introdujo un nuevo parámetro haciendo esto automáticamente: w3schools.com/tags/att_iframe_srcdoc.asp El único problema es la compatibilidad del navegador ...
Vincent Audebert
posible duplicado de poner html dentro de un iframe (usando javascript)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

121

La configuración srcde un recién creado iframeen javascript no activa el analizador HTML hasta que el elemento se inserte en el documento. El HTML se actualiza y se invoca el analizador HTML y procesa el atributo como se esperaba.

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

Además, para responder a su pregunta, es importante tener en cuenta que este enfoque tiene problemas de compatibilidad con algunos navegadores, consulte la respuesta de @mschr para obtener una solución de navegador cruzado.

GillesC
fuente
3
Ni siquiera funciona en IE10. La respuesta de @ mschr funciona en IE7 + con seguridad, tal vez incluso versiones anteriores.
James M. Greene
66
Su pregunta fue "¿Qué estoy pasando por alto?" y ese fue el hecho de que este iframe no se agregó al documento. Nunca afirmo que se trata de navegadores cruzados y solo más de un año después de responder que alguien realmente se quejó. No, no es un navegador cruzado. Pero seamos honestos, si quieres calidad de código, probablemente haya una solución mucho más limpia que usar un iframe en primer lugar :)
GillesC
1
Tenga en cuenta que encodeURI(...)no codifica todos los caracteres, por lo que si su HTML tiene caracteres especiales como #, esto se romperá. encodeURIComponent(...)codifica estos caracteres. Ver esta respuesta
Ryan Morlok
237

A pesar de que tu src = encodeURIdeberías funcionar, yo habría tomado un camino diferente:

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

Como esto no tiene restricciones de dominio x y se realiza completamente a través del iframecontrolador, puede acceder y manipular el contenido del marco más adelante. Todo lo que debe asegurarse es que los contenidos se hayan procesado, lo que (según el tipo de navegador) comenzará durante / después de que se emita el comando .write, pero no necesariamente cuando close()se llama.

Una forma 100% compatible de hacer una devolución de llamada podría ser este enfoque:

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

Iframes tiene el evento onload, sin embargo. Aquí hay un enfoque para acceder al html interno como DOM (js):

iframe.onload = function() {
   var div=iframe.contentWindow.document.getElementById('mydiv');
};
mschr
fuente
Interesante; No había visto esta técnica antes. Sé que la codificación / decodificación URI agrega un golpe de rendimiento, pero también lo he visto descrito como la única alternativa en entornos que no admiten la srcdocpropiedad . ¿Hay un inconveniente en el document.writeenfoque?
Trevor Burnham
1
@mschr ¿Este método admite el código de página HTML completo donde también se incluyen las hojas de estilo y las inclusiones? Consulte stackoverflow.com/questions/19871886
1.21 gigavatios del
2
Básicamente, sí, creo que debería, comentaré allí. La técnica básicamente abre un flujo de entrada de texto en el que puede escribir mediante document.write. A su vez, una página web de carga normal lo recupera a través de un flujo de websocket.
mschr
2
Esto funciona en Internet Explorer! Eso es útil ya que los URI de datos no se pueden usar como fuentes de iframe en ninguna versión de IE. caniuse.com/#feat=datauri
Jesse Hallett
2
Es interesante que document.body.appendChild(iframe)se requiera para que esto funcione.
Paul
14

Gracias por tu gran pregunta, esto me ha pillado varias veces. Cuando uso la fuente HTML dataURI, encuentro que tengo que definir un documento HTML completo.

Vea a continuación un ejemplo modificado.

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

tome nota del contenido html envuelto con <html>etiquetas y la iframe.srccadena.

El elemento iframe debe agregarse al árbol DOM para analizarse.

document.body.appendChild(iframe);

No podrá inspeccionar a iframe.contentDocumentmenos que esté disable-web-securityen su navegador. Recibirás un mensaje

DOMException: no se pudo leer la propiedad 'contentDocument' de 'HTMLIFrameElement': bloqueó un marco con el origen " http: // localhost: 7357 " para que no acceda a un marco de origen cruzado.

kylewelsby
fuente
12

Existe una alternativa para crear un iframe cuyo contenido es una cadena de HTML: el atributo srcdoc . Esto no es compatible con navegadores antiguos (el principal de ellos: ¿ Internet Explorer y posiblemente Safari ?), Pero hay un polyfill para este comportamiento, que podría poner en comentarios condicionales para IE, o usar algo como has.js para condicionalmente perezoso cárgalo.

zedd45
fuente
2
El soporte para esto es bastante común ahora (guardar IE). Y esto es definitivamente preferible a acceder a contentDocument directamente, especialmente porque si se usa junto con el atributo sandbox, no puede acceder a contentDocument.
CambridgeMike
1

Sé que esta es una pregunta antigua, pero pensé que proporcionaría un ejemplo usando el srcdocatributo, ya que ahora es ampliamente compatible y esta pregunta se ve a menudo.

Usando el srcdocatributo, puede proporcionar HTML en línea para incrustar. Anula el srcatributo si es compatible. El navegador volverá al srcatributo si no es compatible.

También recomendaría usar el sandboxatributo para aplicar restricciones adicionales al contenido en el marco. Esto es especialmente importante si el HTML no es tuyo.

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

Si necesita admitir navegadores antiguos, puede buscar srcdocasistencia y recurrir a uno de los otros métodos a partir de otras respuestas.

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

frobinsonj
fuente
1

El enfoque de URL solo funcionará para pequeños fragmentos HTML. El enfoque más sólido es generar una URL de objeto a partir de un blob y usarlo como fuente del iframe dinámico.

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);
damoeb
fuente
0

Hacer esto

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

var frame_win = getIframeWindow(el);

console.log(frame_win);
...

getIframeWindow se define aquí

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;
}
Dominique Fortin
fuente