Dynamiczne tworzenie elementu iframe z podanym kodem HTML

148

Próbuję utworzyć element iframe z JavaScript i wypełnić go dowolnym kodem HTML, na przykład:

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

Spodziewałbym iframesię wtedy zawierać prawidłowe okno i dokument. Jednak tak nie jest:

> console.log (iframe.contentWindow);
zero

Wypróbuj sam: http://jsfiddle.net/TrevorBurnham/9k9Pe/

Co ja przeoczam?

Trevor Burnham
źródło
9
Zauważ, że HTML5 wprowadził nowy parametr, robiąc to automatycznie: w3schools.com/tags/att_iframe_srcdoc.asp Jedynym problemem jest kompatybilność przeglądarki ...
Vincent Audebert,
możliwy duplikat umieszczania html w
ramce

Odpowiedzi:

121

Ustawienie srcnowo utworzonego iframew javascript nie uruchamia parsera HTML, dopóki element nie zostanie wstawiony do dokumentu. Kod HTML jest następnie aktualizowany, a parser HTML zostanie wywołany i przetworzy atrybut zgodnie z oczekiwaniami.

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

Również w odpowiedzi na twoje pytanie ważne jest, aby pamiętać, że to podejście wiąże się z problemami ze zgodnością z niektórymi przeglądarkami, zapoznaj się z odpowiedzią @mschr, aby uzyskać rozwiązanie dla różnych przeglądarek.

GillesC
źródło
3
Nie działa nawet w IE10. Odpowiedź @ mschr na pewno działa w IE7 +, może nawet w starszych wersjach.
James M. Greene
6
Jego pytanie brzmiało: „Co ja przeoczam?” i to był fakt, że iframe nie zostało dołączone do dokumentu. Nigdy nie twierdzę, że było to między przeglądarkami i dopiero ponad rok po tym, jak odpowiedziałem, że ktoś faktycznie narzekał. Nie, to nie jest cross browser. Ale bądźmy szczerzy, jeśli zależy Ci na jakości kodu, prawdopodobnie istnieje znacznie czystsze rozwiązanie niż użycie iframe w pierwszej kolejności :)
GillesC
1
Pamiętaj, że encodeURI(...)nie koduje wszystkich znaków, więc jeśli Twój kod HTML zawiera znaki specjalne, takie jak #, to się zepsuje. encodeURIComponent(...)koduje te znaki. Zobacz tę odpowiedź
Ryan Morlok
237

Chociaż src = encodeURIpowinieneś działać, poszedłbym inną drogą:

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

Ponieważ nie ma to ograniczeń w dziedzinie x-domeny i odbywa się to całkowicie za pośrednictwem iframeuchwytu, możesz później uzyskać dostęp i manipulować zawartością ramki. Wystarczy upewnić się, że zawartość została wyrenderowana, co (w zależności od typu przeglądarki) uruchomi się podczas / po wydaniu polecenia .write - ale nie jest to konieczne, gdy close()zostanie wywołane.

W 100% kompatybilnym sposobem wywołania zwrotnego może być takie podejście:

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

Jednak iframes zawiera zdarzenie onload. Oto podejście do uzyskania dostępu do wewnętrznego html jako DOM (js):

iframe.onload = function() {
   var div=iframe.contentWindow.document.getElementById('mydiv');
};
mschr
źródło
Ciekawy; Nie widziałem wcześniej tej techniki. Wiem, że kodowanie / dekodowanie URI zwiększa wydajność, ale widziałem to również jako jedyną alternatywę w środowiskach, które nie obsługują tej srcdocwłaściwości . Czy jest jakaś wada tego document.writepodejścia?
Trevor Burnham
1
@mschr Czy ta metoda obsługuje pełny kod strony HTML, w której ładowane są również pliki dołączeń i arkusze stylów? Zobacz stackoverflow.com/questions/19871886
1,21 gigawatów
2
Zasadniczo tak, uważam, że powinien, źle tam komentować. Technika ta w zasadzie otwiera wejściowy strumień tekstowy, do którego można pisać przez document.write. Z kolei normalna wczytująca się strona internetowa pobiera to przez strumień sieciowy.
mschr
2
Działa to w przeglądarce Internet Explorer! Jest to przydatne, ponieważ identyfikatorów URI danych nie można używać jako źródeł iframe w żadnej wersji IE. caniuse.com/#feat=datauri
Jesse Hallett
2
Ciekawe, że document.body.appendChild(iframe)jest to wymagane, aby to działało.
Paul
14

Dzięki za świetne pytanie, kilka razy mnie to zaskoczyło. Używając źródła dataURI HTML, stwierdzam, że muszę zdefiniować pełny dokument HTML.

Zobacz poniżej zmodyfikowany przykład.

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

zwróć uwagę na zawartość html owiniętą <html>tagami i iframe.srcciągiem.

Element iframe musi zostać dodany do drzewa DOM, aby można go było przeanalizować.

document.body.appendChild(iframe);

Nie będziesz w stanie sprawdzić, iframe.contentDocumentchyba że disable-web-securityw swojej przeglądarce. Otrzymasz wiadomość

DOMException: nie udało się odczytać właściwości „contentDocument” z „HTMLIFrameElement”: zablokowano ramce o źródle „ http: // localhost: 7357 ” dostęp do ramki pochodzącej z innych źródeł.

kylewelsby
źródło
12

Istnieje alternatywa tworzenia elementu iframe, którego zawartość jest ciągiem znaków HTML: atrybut srcdoc . Nie jest to obsługiwane w starszych przeglądarkach (między innymi: Internet Explorer i być może Safari ?), Ale istnieje polifill dla tego zachowania, który można umieścić w komentarzach warunkowych dla IE lub użyć czegoś takiego jak has.js do warunkowego leniwości Załaduj To.

zedd45
źródło
2
wsparcie dla tego jest teraz dość powszechne (z wyjątkiem IE). Jest to zdecydowanie lepsze rozwiązanie niż bezpośredni dostęp do contentDocument - zwłaszcza, że ​​jeśli jest używany w połączeniu z atrybutem sandbox, nie można uzyskać dostępu do contentDocument.
CambridgeMike
1

Wiem, że to stare pytanie, ale pomyślałem, że podam przykład, używając srcdocatrybutu, ponieważ jest to obecnie szeroko obsługiwane i jest to pytanie często wyświetlane.

Korzystając z srcdocatrybutu, możesz dostarczyć wbudowany kod HTML do osadzenia. Zastępuje srcatrybut, jeśli jest obsługiwany. Przeglądarka powróci do srcatrybutu, jeśli nie jest obsługiwany.

Poleciłbym również użycie sandboxatrybutu, aby zastosować dodatkowe ograniczenia do treści w ramce. Jest to szczególnie ważne, jeśli kod HTML nie jest Twoim własnym.

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

Jeśli potrzebujesz obsługi starszych przeglądarek, możesz sprawdzić, czy są srcdocobsługiwane, i wrócić do jednej z pozostałych metod z innych odpowiedzi.

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
źródło
1

Podejście do adresu URL będzie działać tylko w przypadku małych fragmentów HTML. Bardziej solidnym podejściem jest wygenerowanie adresu URL obiektu z obiektu blob i użycie go jako źródła dynamicznej ramki 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);
damoeb
źródło
0

Zrób to

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

var frame_win = getIframeWindow(el);

console.log(frame_win);
...

getIframeWindow zdefiniowano

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
źródło