Créer dynamiquement un iframe avec du HTML donné

148

J'essaie de créer un iframe à partir de JavaScript et de le remplir avec du HTML arbitraire, comme ceci:

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

Je m'attendrais iframeà contenir alors une fenêtre et un document valides. Cependant, ce n'est pas le cas:

> console.log (iframe.contentWindow);
nul

Essayez-le par vous-même: http://jsfiddle.net/TrevorBurnham/9k9Pe/

Qu'est-ce que je surplombe?

Trevor Burnham
la source
9
Notez que HTML5 a introduit un nouveau paramètre faisant cela automatiquement: w3schools.com/tags/att_iframe_srcdoc.asp Le seul problème est la compatibilité du navigateur ...
Vincent Audebert
duplication possible de la mise en html dans une iframe (en utilisant javascript)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

121

La définition de la valeur srcd'un nouvellement créé iframeen javascript ne déclenche pas l'analyseur HTML tant que l'élément n'est pas inséré dans le document. Le HTML est ensuite mis à jour et l'analyseur HTML sera appelé et traitera l'attribut comme prévu.

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

Aussi cette réponse à votre question, il est important de noter que cette approche a des problèmes de compatibilité avec certains navigateurs, veuillez consulter la réponse de @mschr pour une solution multi-navigateurs.

GillesC
la source
3
Ne fonctionne même pas dans IE10. La réponse de @ mschr fonctionne bien sûr dans IE7 +, peut-être même dans les versions plus anciennes.
James M. Greene
6
Sa question était "Qu'est-ce que je néglige?" et c'était le fait que l'iframe n'était pas annexé au document. Je n'ai jamais prétendu qu'il s'agissait de navigateurs croisés et ce n'est que plus d'un an après avoir répondu que quelqu'un s'est plaint. Non, ce n'est pas un navigateur croisé. Mais soyons honnêtes si vous voulez la qualité du code, il y a probablement une solution beaucoup plus propre que d'utiliser une iframe en premier lieu :)
GillesC
1
Notez que encodeURI(...)cela n'encode pas tous les caractères, donc si votre HTML a des caractères spéciaux comme #, cela se cassera. encodeURIComponent(...)encode ces caractères. Voir cette réponse
Ryan Morlok
237

Bien que vous ayez src = encodeURIdû fonctionner, j'aurais choisi une voie différente:

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

Comme cela n'a pas de contraintes de domaine x et est entièrement effectué via la iframepoignée, vous pouvez accéder et manipuler le contenu du cadre plus tard. Tout ce dont vous devez vous assurer est que le contenu a bien été rendu, ce qui (selon le type de navigateur) démarrera pendant / après l'émission de la commande .write - mais pas nécessairement lors de l' close()appel.

Une manière 100% compatible de faire un rappel pourrait être cette approche:

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

Iframes a cependant l'événement onload. Voici une approche pour accéder au html interne en tant que DOM (js):

iframe.onload = function() {
   var div=iframe.contentWindow.document.getElementById('mydiv');
};
mschr
la source
Intéressant; Je n'avais jamais vu cette technique auparavant. Je sais que le codage / décodage URI ajoute un impact sur les performances, mais je l'ai également vu comme la seule alternative dans les environnements qui ne prennent pas en charge la srcdocpropriété . Y a-t-il un inconvénient à l' document.writeapproche?
Trevor Burnham
1
@mschr Cette méthode prend-elle en charge le code de page HTML complet où les inclusions et les feuilles de style sont également chargées? Voir stackoverflow.com/questions/19871886
1,21 gigawatts
2
Fondamentalement, oui je crois qu'il devrait, mauvais commentaire là-bas. La technique ouvre essentiellement un flux de texte d'entrée dans lequel vous pouvez écrire via document.write. À son tour, une page Web de chargement normal le récupère via un flux Websocket.
mschr
2
Cela fonctionne dans Internet Explorer! C'est pratique car les URI de données ne peuvent pas être utilisés comme sources iframe dans aucune version d'IE. caniuse.com/#feat=datauri
Jesse Hallett
2
Intéressant que le document.body.appendChild(iframe)est nécessaire pour que cela fonctionne.
Paul
14

Merci pour votre excellente question, cela m'a surpris à quelques reprises. Lors de l'utilisation de la source HTML dataURI, je trouve que je dois définir un document HTML complet.

Voir ci-dessous un exemple modifié.

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

prenez note du contenu html enveloppé de <html>balises et de la iframe.srcchaîne.

L'élément iframe doit être ajouté à l'arborescence DOM pour être analysé.

document.body.appendChild(iframe);

Vous ne pourrez pas inspecter le iframe.contentDocumentsauf si vous êtes disable-web-securitysur votre navigateur. Vous recevrez un message

DOMException: échec de lecture de la propriété 'contentDocument' à partir de 'HTMLIFrameElement': blocage d'un cadre avec l'origine " http: // localhost: 7357 " d'accéder à un cadre d'origine croisée.

Kylewelsby
la source
12

Il existe une alternative pour créer une iframe dont le contenu est une chaîne HTML: l'attribut srcdoc . Cela n'est pas pris en charge dans les navigateurs plus anciens (le principal d'entre eux: Internet Explorer et éventuellement Safari ?), Mais il existe un polyfill pour ce comportement, que vous pouvez mettre dans des commentaires conditionnels pour IE, ou utiliser quelque chose comme has.js pour conditionnellement paresseux Charge le.

zedd45
la source
2
le support pour cela est assez courant maintenant (sauf IE). Et cela est certainement préférable à l'accès direct à contentDocument - d'autant plus que s'il est utilisé en conjonction avec l'attribut sandbox, vous ne pouvez pas accéder à contentDocument.
CambridgeMike
1

Je sais que c'est une vieille question, mais j'ai pensé que je donnerais un exemple en utilisant l' srcdocattribut car il est maintenant largement pris en charge et cette question est souvent vue.

En utilisant l' srcdocattribut, vous pouvez fournir du HTML en ligne à intégrer. Il remplace l' srcattribut s'il est pris en charge. Le navigateur reviendra à l' srcattribut s'il n'est pas pris en charge.

Je recommanderais également d'utiliser l' sandboxattribut pour appliquer des restrictions supplémentaires au contenu dans le cadre. Ceci est particulièrement important si le HTML n'est pas le vôtre.

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

Si vous avez besoin de prendre en charge des navigateurs plus anciens, vous pouvez vérifier la srcdocprise en charge et revenir à l'une des autres méthodes à partir d'autres réponses.

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
la source
1

L'approche URL ne fonctionnera que pour les petits fragments HTML. L'approche la plus solide consiste à générer une URL d'objet à partir d'un objet blob et à l'utiliser comme source de l'iframe dynamique.

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
la source
0

Faire ça

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

var frame_win = getIframeWindow(el);

console.log(frame_win);
...

getIframeWindow est défini ici

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
la source