Criando um iframe dinamicamente com HTML fornecido

148

Estou tentando criar um iframe a partir do JavaScript e preenchê-lo com HTML arbitrário, da seguinte forma:

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

Eu esperaria iframeentão conter uma janela e um documento válidos. No entanto, este não é o caso:

> console.log (iframe.contentWindow);
nulo

Experimente você mesmo: http://jsfiddle.net/TrevorBurnham/9k9Pe/

O que eu estou negligenciando?

Trevor Burnham
fonte
9
Note-se que HTML5 introduziu um novo parâmetro de fazer isso automaticamente: w3schools.com/tags/att_iframe_srcdoc.asp O único problema é a compatibilidade do navegador ...
Vincent Audebert
possível duplicação de colocar o html dentro de um iframe (usando javascript)
Ciro Santilli 16 法轮功 病 六四 事件

Respostas:

121

A configuração srcde um iframejavascript recém-criado não aciona o analisador HTML até que o elemento seja inserido no documento. O HTML é atualizado e o analisador de HTML será chamado e processará o atributo conforme o esperado.

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

Além disso, para responder à sua pergunta, é importante observar que essa abordagem tem problemas de compatibilidade com alguns navegadores. Consulte a resposta de @mschr para obter uma solução entre navegadores.

GillesC
fonte
3
Nem funciona no IE10. A resposta do @ mschr funciona no IE7 +, com certeza, talvez até em versões mais antigas.
James M. Greene
6
Sua pergunta era "O que eu estou ignorando?" e esse foi o fato de que iframe não foi anexado ao documento. Eu nunca afirmo que era entre navegadores e é apenas mais de um ano depois que eu respondi que alguém realmente reclamou. Não, não é um navegador cruzado. Mas vamos ser honestos, se você quiser a qualidade do código provavelmente há uma solução mais limpa muito mais do que usar um iframe, em primeiro lugar :)
GillesC
1
Observe que encodeURI(...)não codifica todos os caracteres; portanto, se seu HTML tiver caracteres especiais como #, isso será interrompido. encodeURIComponent(...)codifica esses caracteres. Veja esta resposta
Ryan Morlok 19/02
237

Embora você src = encodeURIdeva funcionar, eu teria seguido um caminho 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 isso não possui restrições no domínio x e é completamente feito por meio do iframeidentificador, você pode acessar e manipular o conteúdo do quadro posteriormente. Tudo o que você precisa é ter certeza de que o conteúdo foi renderizado, o que (dependendo do tipo do navegador) será iniciado durante / após a emissão do comando .write - mas não será necessário quando close()for chamado.

Uma maneira 100% compatível de fazer um retorno de chamada pode ser esta abordagem:

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

O Iframes possui o evento onload, no entanto. Aqui está uma abordagem para acessar o html interno como DOM (js):

iframe.onload = function() {
   var div=iframe.contentWindow.document.getElementById('mydiv');
};
mschr
fonte
Interessante; Eu não tinha visto essa técnica antes. Eu sei que a codificação / decodificação de URI adiciona um impacto no desempenho, mas também a vi descrita como a única alternativa em ambientes que não suportam a srcdocpropriedade . Existe uma desvantagem na document.writeabordagem?
Trevor Burnham
1
@mschr Este método também suporta código de página HTML completo, onde são incluídas e folhas de estilo também carregadas? Veja stackoverflow.com/questions/19871886
1,21 gigawatts
2
Basicamente, sim, eu acredito que deveria, vou comentar lá. A técnica basicamente abre um fluxo de entrada de texto no qual você pode gravar via document.write. Por sua vez, uma página de carregamento normal recupera isso por meio de um fluxo de soquete da web.
mschr
2
Isso funciona no Internet Explorer! Isso é útil, pois os URIs de dados não podem ser usados ​​como fontes iframe em nenhuma versão do IE. caniuse.com/#feat=datauri
Jesse Hallett
2
Interessante document.body.appendChild(iframe)é o necessário para que isso funcione.
Paul
14

Obrigado pela sua ótima pergunta, isso me surpreendeu algumas vezes. Ao usar a fonte HTML dataURI, acho que preciso definir um documento HTML completo.

Veja abaixo um exemplo 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);

anote o conteúdo html envolvido com <html>tags e a iframe.srcstring.

O elemento iframe precisa ser adicionado à árvore DOM para ser analisado.

document.body.appendChild(iframe);

Você não poderá inspecionar a iframe.contentDocumentmenos que esteja disable-web-securityno seu navegador. Você receberá uma mensagem

DOMException: falha ao ler a propriedade 'contentDocument' de 'HTMLIFrameElement': bloqueou um quadro com origem " http: // localhost: 7357 " ao acessar um quadro de origem cruzada.

kylewelsby
fonte
12

Existe uma alternativa para criar um iframe cujo conteúdo seja uma sequência de HTML: o atributo srcdoc . Isso não é suportado em navegadores mais antigos (principalmente entre eles: Internet Explorer e, possivelmente, Safari ?), Mas há um polyfill para esse comportamento, que você pode colocar em comentários condicionais para o IE ou usar algo como has.js para obter uma condição preguiçosa. carregue-o.

zedd45
fonte
2
o suporte para isso é bastante popular agora (salve o IE). E isso é definitivamente preferível a acessar o contentDocument diretamente - especialmente porque se usado em conjunto com o atributo sandbox, você não pode acessar o contentDocument.
CambridgeMike
1

Eu sei que essa é uma pergunta antiga, mas pensei em fornecer um exemplo usando o srcdocatributo, pois agora isso é amplamente suportado e essa pergunta é vista com frequência.

Usando o srcdocatributo, você pode fornecer HTML embutido para incorporar. Ele substitui o srcatributo, se suportado. O navegador retornará ao srcatributo se não for suportado.

Eu também recomendaria o uso do sandboxatributo para aplicar restrições extras ao conteúdo no quadro. Isso é especialmente importante se o HTML não for seu.

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

Se você precisar oferecer suporte a navegadores mais antigos, poderá procurar srcdocsuporte e fallback para um dos outros métodos de outras respostas.

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
fonte
1

A abordagem de URL funcionará apenas para pequenos relacionamentos de HTML. A abordagem mais sólida é gerar um URL de objeto a partir de um blob e usá-lo como fonte do 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
fonte
0

Faça isso

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

var frame_win = getIframeWindow(el);

console.log(frame_win);
...

getIframeWindow é definido aqui

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
fonte