<iframe>-Elemente, die Inhalte von verschiedenen Domains anzeigen, verfügen über Sicherheitsmechanismen, die verschiedene Dinge verhindern. Zum Beispiel kann JavaScript nicht auf Inhalte innerhalb eines solchen Elements zugreifen. Das kann sehr frustrierend sein, wenn man nur etwas Normales und Gutartiges tun möchte, wie z.B. die Höhe des iframes an den Inhalt anzupassen. Diese Sicherheitsmaßnahmen sind dazu da, böswillige Dinge zu verhindern, die man tun könnte, wenn man doch JavaScript-Zugriff auf die inneren Abläufe eines iframes hätte.
Ich habe buchstäblich *jahrelang* an verschiedenen Lösungen dafür gearbeitet und bin immer wieder gescheitert. Vor kurzem bin ich auf eine Lösung von Kazi Manzur Rashid (etwa zwei Jahre alt) gestoßen, die ziemlich solide aussieht, also dachte ich, ich probiere sie aus. Die Ergebnisse sind die besten, die ich bisher erzielen konnte.
Warnung: Die Demo gerät bei WebKit-Browsern wie Safari und Chrome etwas aus dem Ruder, siehe Probleme unten.
An diejenigen, die vor mir kamen...
Um dies mit einem iframe zu tun, dessen Quellinhalt sich auf derselben Domain befindet, können Sie dies tun. Same-Domain-Iframes unterliegen nicht denselben Einschränkungen, daher ist es viel einfacher.
Adam Fortuna hat einige Optionen unter Verwendung einer Art Man-in-the-Middle-Idee untersucht. Dies wurde möglicherweise durch eine Technik von John McKerrell inspiriert.
Die folgende Technik benötigt jedoch keine Zwischenperson, weshalb sie dem Ideal näher kommt.
Voraussetzungen
Diese Lösung setzt voraus, dass Sie sowohl die Hosting-Seite als auch die Quellseite kontrollieren. Sie müssen auf beiden Seiten JavaScript ausführen. Dies wird also nicht für einen iframe von google.com funktionieren.
Die große Idee
Die Umgehungslösung verwendet Hashtags in der URL, um Informationen hin und her zu übermitteln. Dies umgeht die Sicherheitsbeschränkungen. Es ist unwahrscheinlich, dass dies jemals fehlschlägt, daher ist es keine "Hackerei". Mit einem reinen Hashtag kann man nichts Böswilliges tun. In unserem Fall lesen wir diese Informationen nur ein und verwenden sie zur Größenanpassung.
Die HOST-Domain
Enthält tatsächlich den iframe
<iframe id="frame-one" scrolling="no" frameborder="0" src="http://digwp.com/examples/iFrameSource/source.html" onload="FrameManager.registerFrame(this)"></iframe>
Der iframe hat ein onload-Ereignis, das eine Funktion aus der FrameManager-Klasse aufruft, die wir im <head> aufrufen müssen
<script type="text/javascript" src="js/FrameManager.js"></script>
Und hier ist die magische FrameManager-Klasse
var FrameManager = {
currentFrameId : '',
currentFrameHeight : 0,
lastFrameId : '',
lastFrameHeight : 0,
resizeTimerId : null,
init: function() {
if (FrameManager.resizeTimerId == null) {
FrameManager.resizeTimerId = window.setInterval(FrameManager.resizeFrames, 500);
}
},
resizeFrames: function() {
FrameManager.retrieveFrameIdAndHeight();
if ((FrameManager.currentFrameId != FrameManager.lastFrameId) || (FrameManager.currentFrameHeight != FrameManager.lastFrameHeight)) {
var iframe = document.getElementById(FrameManager.currentFrameId.toString());
if (iframe == null) return;
iframe.style.height = FrameManager.currentFrameHeight.toString() + "px";
FrameManager.lastFrameId = FrameManager.currentFrameId;
FrameManager.lastFrameHeight = FrameManager.currentFrameHeight;
window.location.hash = '';
}
},
retrieveFrameIdAndHeight: function() {
if (window.location.hash.length == 0) return;
var hashValue = window.location.hash.substring(1);
if ((hashValue == null) || (hashValue.length == 0)) return;
var pairs = hashValue.split('&');
if ((pairs != null) && (pairs.length > 0)) {
for(var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
if ((pair != null) && (pair.length > 0)) {
if (pair[0] == 'frameId') {
if ((pair[1] != null) && (pair[1].length > 0)) {
FrameManager.currentFrameId = pair[1];
}
} else if (pair[0] == 'height') {
var height = parseInt(pair[1]);
if (!isNaN(height)) {
FrameManager.currentFrameHeight = height;
FrameManager.currentFrameHeight += 15;
}
}
}
}
}
},
registerFrame: function(frame) {
var currentLocation = location.href;
var hashIndex = currentLocation.indexOf('#');
if (hashIndex > -1) {
currentLocation = currentLocation.substring(0, hashIndex);
}
frame.contentWindow.location = frame.src + '?frameId=' + frame.id + '#' + currentLocation;
}
};
window.setTimeout(FrameManager.init, 300);
Die SOURCE-Seite
Der Quellinhalt könnte so ziemlich alles sein, auf einem anderen Server. Vielleicht
<body>
<div id="content">
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce in tortor sit amet sem luctus ornare. Nam sed augue id erat commodo gravida. Nulla in pede. Nunc sed elit non pede aliquam eleifend. Cras varius. Sed non lorem eget ipsum accumsan suscipit. Donec bibendum enim. Phasellus a ligula. Fusce turpis diam, ultricies at, ullamcorper a, consectetuer et, mauris. Pellentesque neque felis, scelerisque non, vestibulum at, luctus quis, velit. Quisque sit amet mi sed sem facilisis ornare. In leo ante, hendrerit nec, lobortis eget, feugiat ac, orci.
</div>
</body>
Das Wichtigste, was wir auf der Quellseite tun, ist, etwas JavaScript auszuführen, um die Höhe der Seite zu "veröffentlichen". In meiner Demo füge ich auch etwas jQuery hinzu, um eine Schriftgrößenanimation durchzuführen, sodass der Quellinhalt höher und kürzer wird.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js?ver=1.3.2"></script>
<script type="text/javascript" src="frame.js"></script>
<script type="text/javascript">
window.onload = function(event) {
window.setInterval(publishHeight, 300);
}
$(function() {
var $content = $("#content");
function toggleFontSize() {
if ($content.css("font-size") == "22px") {
$("#content").animate({
fontSize: "15px"
});
} else {
$("#content").animate({
fontSize: "22px"
});
}
}
var int = setInterval(toggleFontSize, 5000);
});
</script>
Wir rufen also publishHeight() alle 300 Millisekunden auf. Hier ist diese Funktion und ihre etwas zusammengewürfelte Truppe von unterstützenden Funktionen aus der Datei frame.js.
function publishHeight() {
if (window.location.hash.length == 0) return;
var frameId = getFrameId();
if (frameId == '') return;
var actualHeight = getBodyHeight();
var currentHeight = getViewPortHeight();
if (Math.abs(actualHeight - currentHeight) > 15) {
var hostUrl = window.location.hash.substring(1);
hostUrl += "#";
hostUrl += 'frameId=' + frameId;
hostUrl += '&';
hostUrl += 'height=' + actualHeight.toString();
window.top.location = hostUrl;
}
}
function getFrameId() {
var qs = parseQueryString(window.location.href);
var frameId = qs["frameId"];
var hashIndex = frameId.indexOf('#');
if (hashIndex > -1) {
frameId = frameId.substring(0, hashIndex);
}
return frameId;
}
function getBodyHeight() {
var height,
scrollHeight,
offsetHeight;
if (document.height) {
height = document.height;
} else if (document.body) {
if (document.body.scrollHeight) {
height = scrollHeight = document.body.scrollHeight;
}
if (document.body.offsetHeight) {
height = offsetHeight = document.body.offsetHeight;
}
if (scrollHeight && offsetHeight) {
height = Math.max(scrollHeight, offsetHeight);
}
}
return height;
}
function getViewPortHeight() {
var height = 0;
if (window.innerHeight) {
height = window.innerHeight - 18;
} else if ((document.documentElement) && (document.documentElement.clientHeight)) {
height = document.documentElement.clientHeight;
} else if ((document.body) && (document.body.clientHeight)) {
height = document.body.clientHeight;
}
return height;
}
function parseQueryString(url) {
url = new String(url);
var queryStringValues = new Object(),
querystring = url.substring((url.indexOf('?') + 1), url.length),
querystringSplit = querystring.split('&');
for (i = 0; i < querystringSplit.length; i++) {
var pair = querystringSplit[i].split('='),
name = pair[0],
value = pair[1];
queryStringValues[name] = value;
}
return queryStringValues;
}
Probleme
- Bei WebKit fröhliches Aktualisieren. Anscheinend war es früher Firefox, das bei jeder Aktualisierung fröhlich wurde, aber offenbar hat sich das mit der neuesten Version umgekehrt. Seien Sie vorsichtig, wenn Sie die Demo in Safari oder Chrome besuchen, sie ist etwas ruckelig. Wenn jemand Ideen dazu hat, ist dies wahrscheinlich das größte Problem.
- Macht den Zurück-Button kaputt. Wegen all der Hashtags, die auf der Host-Seite herumfliegen, kann die Zurück-Button-Funktionalität dieser Seite beeinträchtigt werden.
- Intervalle, Intervalle, Intervalle. Es gibt viele Intervalle, die hier herumfliegen und fast immer verdächtig sind. Je schneller die Intervalle, desto flüssiger, aber ressourcenintensiver. Je langsamer, desto ruckeliger, aber einfacher. Beides ist mies.
- Informationslimit über Hash gesendet. Wenn Sie daran gedacht haben, diese Technik zu verwenden, um andere Informationen zu senden, sind Sie durch die Menge der übertragbaren Informationen begrenzt, da dies über die URL geschieht. Vermutlich dasselbe wie bei einer GET-Anfrage... etwa 1 KB.
Der heilige Gral
Ich glaube, der Grund, warum ich so besessen davon bin, ist, dass Wufoo-Formulare dies scheinbar perfekt handhaben. Wufoo-Formulare konnten früher nur mit iframes eingebettet werden. Ich musste ihre Höhe buchstäblich fast 50 % größer einstellen als der Inhalt selbst, um die Erweiterung des Inhalts bei Fehlermeldungen nach dem Absenden des Formulars zu berücksichtigen (die Fehlermeldungen erweiterten die Höhe). Wenn ich das nicht tat, wurde der Absende-Button abgeschnitten, was das Formular nicht absendbar machte.
Wufoo bietet jetzt eine JavaScript-Einbettungsoption an, aber letztendlich kommt das Formular immer noch über einen iframe. Wie auch immer sie es machen, der iframe kann sich magischerweise nach Bedarf anpassen. Ich habe keine Ahnung, wie es gemacht wird, aber ich stelle mir vor, es ist etwas Ähnliches wie das, was wir hier tun. Da Wufoo Zugriff auf beide Seiten hat, die Host-Seite und die Quellseite. Meine beste Vermutung ist, dass das JavaScript auf der Host-Seite Anfragen an die Quellseite senden kann, die der Quellseite irgendwie genau mitteilen kann, welche Höhe sie haben sollte.
Wurde es besser?
Es ist viel Code, aber hey, es funktioniert (nochmals vielen Dank an Kazi für die Intelligenz). Wurde es besser? Bitte, teilen Sie es mit.
Update: David Bradshaw hat iframe-resizer veröffentlicht
Eine einfache Bibliothek für die domänenübergreifende Größenanpassung von iFrames an Inhalte mit Unterstützung für Fenstergrößenänderung und mehrere iFrames.
Das funktioniert in jedem Browser, der postMessage unterstützt (IE 8+).
Anstatt Ihre Daten im #hash-Tag zu speichern, könnten Sie sie in der
iframeelment.name
und innerhalb Ihres iframe window.name
Vergaß zu erwähnen, dass window.name eine Grenze von ca. 2/4 MB für IE hat. und 64 MB für Firefox.
Hallo Chris, ich habe im letzten Sommer an einer sehr ähnlichen, aber flexibleren Methode für die domänenübergreifende Iframe-Größenanpassung gearbeitet.
Der Vorteil meines Ansatzes ist, dass a) er allgemeiner ist und für andere Dinge als die Iframe-Größenanpassung verwendet werden kann, b) die Frame-Kommunikation bidirektional funktioniert und c) er bei Verfügbarkeit das neue Standardereignis window.postMessage nutzt.
Schauen Sie gerne vorbei
http://benalman.com/projects/jquery-postmessage-plugin/
– Ben
wirklich schönes Plugin, ich hatte keine Ahnung, dass es bereits so etwas gibt... großartige Arbeit, danke!
Das ist spitze Ben. Das ist es, was ich versucht habe und immer wieder gescheitert bin =)
Wenn HTML5 implementiert ist, wird postMessage() dies sicherlich extrem vereinfachen.
Interessantes Konzept! Eine Sorge, die ich bei der Demo habe, ist die Tatsache, dass Ihr Zurück-Button kaputt ist. Ich musste mich gerade verrückt oft zurückklicken von der Demo.
Dito. Ein Link zurück zum Artikel von der Demo wäre eine gute Idee.
was ist mit diesem ab 5:40, wirklich großartige Sachen, alles davon. :D
Warum sollten wir einen iFrame dafür verwenden? Verwenden Sie ein standardkonformes Objekt-Tag, servieren Sie einen iFrame für IE6, wenn Sie müssen... aber die Verwendung eines iFrame ist keine unterstützte Methode. Beispiel (siehe bedingte Kommentare im Quellcode für die iFrame-Methode)
http://bentlyreserve.com/destination.php
Ernsthaft Leute, das ist etwas veraltet.
Ich hatte ehrlich gesagt keine Ahnung, dass das <object>-Tag so verwendet werden kann. Ich muss mir vorstellen, dass die gleichen Sicherheitsbeschränkungen gelten... Ich muss das testen.
Das tut es, obwohl ich glaube, dass Ihre Umgehungslösung denselben Effekt haben sollte.
Das Problem ist, dass ein iFrame nicht gültig ist (strict), und das Objekt-Tag war immer für diesen Zweck gedacht.
Aber Sie haben jetzt vielleicht einen einfacheren Weg, einige dieser Probleme zu umgehen. iFrame wird immer noch kritisch gesehen, macht aber in HTML5 mit den Attributen 'seamless' und 'sandbox' ein Comeback
http://www.w3schools.com/html5/tag_iframe.asp
Sie werden feststellen, dass Sie in diesem Sandbox-Aufruf 'allow-scripts' als Attribut haben können. Problem gelöst, oder? :) Tests zeigen, dass dies in Chrome und Firefox funktioniert, aber noch nicht in IE.
...und um anzumerken, ich teste keine Größenänderung, sondern nur eine Validierung von JavaScript in HTML5 über einen iframe
Gute Zusammenfassung, interessant zu sehen, wie HTML diese Sicherheitsprobleme mit dem Tag handhaben wird.
Wow! Tolles Werkzeug für Entwickler. Danke!
@Chris – Hmmm… ich frage mich, ob es möglich ist,
CSS IFRAME DOM ELEMENTE (von einem anderen Ort) über jQuery zu ändern? (HTML5) Danke für Ihre Antwort.
Viele Grüße aus Polen
Paweł P.
Ich würde gerne generell wissen, was die Ansichten der Leute zur Verwendung des Elements hinsichtlich der Best Practices sind.
Ich weiß, dass sie Ihren Code ungültig machen, wenn Sie XHTML Strict verwenden, aber sie sind mit XHTML Transitional erlaubt.
Dennoch verwendet ein Großteil des von großen Anbietern (Google...YouTube...) bereitgestellten Einbettungscodes iframes, und HTML5 scheint ihn nicht nur weiterhin zu unterstützen, sondern ihm auch neue Attribute zu verleihen...
Hallo Chris,
Eine Sache, die man nicht vergessen sollte, ist, dass iframes nicht nur zur Anzeige von Dokumenten dienen, die extern zur enthaltenden Seite sind.
Was Sie tun können (und vielleicht macht Wufoo das, obwohl ich es nicht überprüft habe) ist, JavaScript auf der enthaltenden Seite zu verwenden, um ein leeres iframe-Element zu erstellen und in das Dokument innerhalb des iframes zu schreiben, und zwar "on-the-fly". Wenn Sie das tun, wird das iframe-Dokument als auf derselben Domain befindlich betrachtet, und Sie können tun, was Sie wollen, sowohl mit dem iframe-Element als auch mit dem iframe-Dokument.
Auf diese Weise können Sie der body des iframe-Dokuments hinzufügen, was Sie möchten, dann seine Breite und Höhe messen und diese Breite und Höhe dann dem iframe-Element zuweisen.
Also, Problem gelöst! Nun ja, außer dass die Browser-API dafür etwas umständlich ist und es einige seltsame Inkonsistenzen zwischen den Browsern gibt. Deshalb habe ich angefangen, ein jQuery-Plugin namens "AppleOfMyIframe" zu entwickeln, um genau diese Art von Dingen zu behandeln. Ich habe es so eingerichtet, dass sich der iframe automatisch in der Größe anpasst, wenn neuer Inhalt hinzugefügt und entfernt wird.
Schauen Sie sich es gerne an / brechen Sie es / reparieren Sie es / etc.
github.com/premasagar/appleofmyiframe
Rufen Sie es so auf
$.iframe('Lorem ipsum').appendTo('body');(Ich dokumentiere derzeit die verschiedenen verfügbaren Methoden im Projekt-Wiki).
Prem
In meinem Kommentar oben wurde der Codeblock vom Blog-Software analysiert. Er hätte lauten sollen
$.iframe('<div>Lorem ipsum</div>).appendTo('body');Hallo, tolles Beispiel!
Aber ich drehe durch, weil ich versuche, es unter IE6 zum Laufen zu bringen. Die Seite wird immer wieder neu geladen... Gibt es vielleicht ein Problem mit dem Hash-Attribut?
Hallo,
schöner Artikel, wirklich hilfreich.
Ich habe ihn verwendet, er funktioniert sehr gut, aber ich habe ein Problem. Wenn ich auf einen Link innerhalb des iframes klicke, wird die Seite neu geladen und bringt mich zurück zur selben Seite. Gibt es eine Lösung, wo wir dies ohne die setInterval()-Funktion machen können?
danke
Ich habe dasselbe Problem wie Uttam. Haben Sie eine Idee, wie man es beheben kann?
Oh mein Gott, danke.
Hat mir bei einem Kunden den Hintern gerettet, der verlangte, dass eine HTML-Datei mit einer .net-Seite kommuniziert, die ihre Informationen von einer ColdFusion-Website bezog.
Großartige Lösung!
Hallo, ich habe einen Fehler bei der Verwendung Ihres Codes wie "TypeError: frameId is undefined"..?? Kann mir jemand bitte helfen, dieses Problem zu lösen?? Vielen Dank im Voraus.
Wufoo verwendet wahrscheinlich Nachrichtenereignisse, um Informationen von der Kindseite an die Elternseite zu senden. So haben wir es bei ähnlichen Implementierungen gemacht. Es spart unordentliche Hashtags und verrückte Intervalle und ermöglicht eine reibungslose, nahtlose Implementierung. Schauen Sie es sich an! :)
JB
Hallo,
Danke für das Skript, ich habe das Chrome-Problem in dieser Zeile behoben
if (Math.abs(actualHeight – currentHeight) > 15) {
Ich habe 20 statt 15 eingesetzt, und es scheint gut zu funktionieren.
José Tomás Sebastião
Werden diese Optionen funktionieren, wenn man NICHT die Möglichkeit hat, die Kindseite zu bearbeiten? Wir haben einen Webservice, den wir verwenden wollen, aber alle "iframe jQuery resizer" scheinen die Möglichkeit zu erfordern, die Kindseitenelemente zu modifizieren. Die Kindseite in unserem Beispiel ist ein Drittanbieterdienst, also können wir sie nicht modifizieren.
Ihre Einblicke sind sehr willkommen!