Blog

Domänenübergreifend die Größe eines iFrames ändern (cross domain iFrame resizing)

Ich kann mich noch an eine Zeit erinnern, als es hieß, Frames sind 90er oder Frames sind tot. Aktuell kommen bei unseren Kunden immer öfter die Anforderungen auf, doch bitte eine iFrame-Lösung zu implementieren. Macht natürlich immer genau dann Sinn, wenn „fremder“ Inhalt in eine Seite integriert oder ein Modal-Pop-up über einer Seite geöffnet werden soll.

„Fremder“ Inhalt ist hier genau der kritische Punkt. Möchte ich als eingebetteter Inhalt der äußeren Webseite sagen, wie groß ich bin, ist das leider nicht mit den üblichen JavaScript-Methoden möglich.

Wie es ohne Monieren der Browser funktioniert, soll im Blog-Beitrag geklärt werden.

Wo liegt das Problem?

Soll die Größe eines iFrames geändert werden, so muss die Größe des Inhaltes des iFrames ermittelt werden und diese der äußeren Webseite übermittelt werden. jQuery realisiert das in ein paar Zeilen Code.

var resizeIFrame = function(){
	var iFrame = jQuery('#iFrame', parent.document.body);
	var height = jQuery('body').outerHeight(true);
	iFrame.height(height);
};

Wird der Code ausgeführt und der Inhalt des iFrames liegt in derselben Domain wie die äußere Webseite, ist alles gut. Liegt der iFrame jedoch in einer anderen Domain, liefern Browser Fehler wie:

Unsafe JavaScript attempt to access frame with URL
http://localhost/iFrameResize/index.html from frame
with URL https://www.holisticon.de/iFrameResize/content.html.
Domains, protocols and ports must match.

(Safari 5.1.2)

Permission denied to access property 'document'
var iFrame = jQuery('#iFrame', parent.document.body);

(Firefox 9.0.1)

Die Lösung: Posting messages

Um domänenübergreifend iFrames mit den äußeren Seiten kommunizieren zu lassen, wird die Funktion windows.postMessage eingesetzt. Da leider noch nicht alle Browser diese Funktionalität unterstützen, hat Ben Alman ein jQuery Plugin veröffentlicht, das dies auch Browsern ermöglicht, die PostMessage nicht unterstützen.

Dazu werden jQuery und das jQuery postMessage Plugin benötigt.

Um das Skript überall einzusetzen, nutze ich die folgenden zwei Methoden, die eine Erweiterung des „cross domain iframe resize“-Bespiels von Ben Alman sind:

/**
 * Method to send the height of the content to a parent page.
 *
 * @param parent_url url where the message is sent to
 * @returns
 */
var sendContentHeight = function(parent_url) {

	// The first param is serialized using $.param (if not a string)
	// and passed to the parent window.
	// If window.postMessage exists, is is used to send
	// the parameter. Otherwise it is passed in the location hash.
	// The second param is the targetOrigin.
	function setHeight() {
		jQuery.postMessage({
			if_height : jQuery('body').outerHeight(true)
		}, parent_url, parent);
	};

	// Now the DOM has been set up (and the height should be set).
	// Invoke setHeight.
	setHeight();
};

Senden der Höhe. Dieses Script wird in der Seite, die als iFrame eingebettet wird, aufgerufen.

/**
 * Creating an iframe for loading content within.
 *
 * @param id id of element where iFrame will be appended
 *
 * @param url domain of the iFrame content
 * @param path path of iframe content without domain
 * @param initalHeight
 *            initial height of iframe will be overwritten if height
              is sent
 * @param initalWidth
 *            initial width of iframe
 * @returns
 */
var appendDynamicHeightIFrame =
    function(id, url, path, initalHeight, initalWidth) {

    // Keep track of the iframe height.
    var if_height,

    // create src of the iframe
    src = url + path + '#' + encodeURIComponent(document.location.href),

    // Append the iFrame
    iframe = jQuery(
        '<iframe src="' + src + '" frameborder="0" scrolling="no"
         width="' + initalWidth + '" height="'+ initalHeight +
        '"></iframe>').appendTo(id);

    // Setup a callback to handle the dispatched MessageEvent event.
    // In cases where window.postMessage is supported,
    // the passed event will have .data, .origin and .source properties.
    // Otherwise, this will only have the .data property.
    jQuery.receiveMessage(function(e) {
        // Get the height from the passsed data.
        var h = Number(
             e.data.replace(/.*if_height=(d+)(?:&|$)/, '$1'));

        if (!isNaN(h) && h > 0 && h !== if_height) {
            // Height has changed, update the iframe.
            iframe.height(if_height = h);
        }

        // An optional origin URL (Ignored where window.postMessage is
        // unsupported).
    }, url);

};

Dieses Script wird in die äußere Seite integriert. Die Funktion hängt ein iFrame an ein Element, dessen id via Parameter (id) übergeben wird und wartet auf eingehende Nachrichten einer Domain, die ebenfalls via Parameter (url) übergeben wird.

Zu beachten beim Einsatz von Servlet-Filtern etc.

Die receiveMessage-Funktion des Scripts nutzt den optionalen Parameter url, um sicherzustellen, dass nur Nachrichten einer definierten Domain empfangen werden. Dies ist zwar sicher, führt aber dazu, dass nur Nachrichten der Domain empfangen werden, die zuerst im iFrame aufgerufen wurde. Aus diesem Grund ist es ratsam, den optionalen Parameter url einfach wegzulassen.

jQuery.receiveMessage(function(e) {
        // Get the height from the passsed data.
        var h = Number(
             e.data.replace(/.*if_height=(d+)(?:&|$)/, '$1'));

        if (!isNaN(h) && h > 0 && h !== if_height) {
            // Height has changed, update the iframe.
            iframe.height(if_height = h);
        }
});

Eine simple Bespielanwendung kann hier heruntergeladen werden. Um den domänenübergreifenden Fall zu testen, müssen die content.html & content2.html inklusive CSS- und JS-Dateien in einer anderen Domain liegen.

Über den Autor

Avatar

Mr Norman Erck M.Sc. started developing websites as a teen in 1999 driven by his fascination for the possibilities of the rising e-business technologies. He is now a certified ScrumMaster and Enterprise- Software-Architect who has worked on e-business projects for over seven years. He takes the role of a scrum master and architect for IT projects in large companies. He is a speaker on conferences about CDI as well.

14 Kommentare

  1. Hi.
    Interessanter Ansatz!

    Wie würde denn ein Beispiel für die receiveMessage-Funktion aussehen? Also wie würde z.B. der iFrame seine Größe an die umschließende Seite senden?

    VG
    Juri

  2. Hallo Juri,

    die Seite im iFrame sendet die Größe mit der folgenden Methode:

    /**
    * Method to send the height of the content to a parent page.
    *
    * @param parent_url url where the message is sent to
    * @returns
    */
    var sendContentHeight = function(parent_url) {

    // The first param is serialized using $.param (if not a string)
    // and passed to the parent window.
    // If window.postMessage exists, is is used to send
    // the parameter. Otherwise it is passed in the location hash.
    // The second param is the targetOrigin.
    function setHeight() {
    jQuery.postMessage({
    if_height : jQuery(‚body‘).outerHeight(true)
    }, parent_url, parent);
    };

    // Now the DOM has been set up (and the height should be set).
    // Invoke setHeight.
    setHeight();
    };

    Am besten Du lädst Dir mal die Beispielapplikation (https://www.holisticon.de/wp-content/uploads/2012/01/iFrameResize.zip) runter und debuggst das Ganze mit Firebug oder Ähnlichen. Funktioniert natürlich auch auf dem localhost.

    Beste Grüße

    Norman

  3. Hi Norman,

    hast Du das ganze auch mal im IE7 getestet? :)
    Ich habe eben mal meine VMWare gestartet und da funktioniert es im IE7 leider nicht.

    Er macht hängt folgendes an die URL: „#13329321195351&if_height=524“

    Hast Du evtl. eine Idee?

    Danke und viele Grüße
    Chris

  4. Hat sich erledigt, lag an der URL von sendContentHeight. Die hatte ich falsch eingetragen, aber erst beim IE7 bemerkt, da der mit dem Hash arbeitet.

  5. Hi,

    ich hab das jetzt alles mal ausprobiert und feststellen müssen dass es bei größerem content des Iframes also (z.B. 2000px Höhe) nur im IE verlässlich funktioniert.

    Ich hab da einfach mal ne simple Liste mit 4 mal Beispielbild + Lorem Ipsum platziert und musste feststellen das der aktuelle Firefox in der Mitte des 4. Elements aufhört und Webkit (also Safari und Chrome) die Höhe nur bis zum. 2. Element wiedergeben.
    Ich habe auch mal die aktuellste jquery (1.7.2) ausprobiert, leider das gleiche Ergebnis.

    Irgendeine Idee woran das liegen könnte ?

    Robert

  6. Ich noch einmal, entschuldige bitte den doppelten Post in kurzer Zeit – ist aber ein sehr interessantes Konzept, danke für den Beitrag! Folgendes habe ich nun auf der „äußeren Seite“ (die den eingebetteten IFrame enthalten soll, eingefügt:

    var resizeIFrame = function(){
    var iFrame = jQuery(‚#iFrame‘, parent.document.body);
    var height = jQuery(‚body‘).outerHeight(true);
    iFrame.height(height);
    };

    Leider gibt er mir im Frontend hierbei nichts aus – sieht jemand meinen Fehler?

    Grüße
    M.

  7. Ich noch einmal; hat niemand eine Idee, wie ich hierbei vorgehen sollte? Würde mich auch über einen Hinweis vom Autor des Beitrags freuen.

    Grüße
    M.

  8. Hallo Michael,

    in dem Code den du verwendest, nutzt du genau die Zeilen, wie es nicht geht.

    Den Code, wie es geht findest du unter: Die Lösung: Posting messages

    Eigentlich brauchst du in der Seite in der das iFrame eingebettet werden soll einfach nur die von mir im Beispiel bereitgestellte Funktion aufrufen:

    jQuery(jQuery(appendDynamicHeightIFrame(‚#iFrameConatinerDiv‘, ‚http://localhost:8080‘, ‚/iFrameResize/content.html‘, 1000, ‚100%‘)));

    ‚#iFrameConatinerDiv‘ ist die ID des divs in welches das iFrame eingefügt werden soll.
    ‚http://localhost:8080‘ ist die Domain auf der einzubindende iFrame liegt,
    ‚/iFrameResize/content.html‘ ist der Pfad, wo der im IFrame anzuzeigende Inhalt liegt und
    1000 ist die Höhe, die der iFrame hat, wenn keine Höhe vom iFrame zurück kommt und
    ‚100%‘ ist die Breite, die der iFrame hat.

    In den Seiten, die im iFrame angezeigt werden, müssen die folgende Funktion eingebunden werden:

    jQuery(document).ready(function() {
    jQuery(sendContentHeight(‚http://localhost:8080/iFrameResize/index.html‘));

    ‚http://localhost/iFrameResize/index.html‘ gibt dabei die komplette Url der Seite an, in welcher das iFrame eingebunden werden soll.

    Den Code findest du unter:

    https://www.holisticon.de/wp-content/uploads/2012/01/iFrameResize.zip

    Beste Grüße

    Norman

  9. Hallo Norman,

    vielen, vielen Dank für deine Antwort. Aufgrund persönlicher Umstände kam ich bisher nicht dazu, dir zu antworten.

    Ich werde das Ganze sobald ich dazu komme – wieder ausprobieren und dir dann hier eine Rückmeldung geben.

    Danke für deine Antwort in jedem Fall!

    Viele Grüße
    M.

  10. Hallo zusammen,

    cooles Snippet. Funktioniert auch eigentlich ganz einwandfrei. Habe aber in Opera das Problem, das es nicht funktioniert. Gibt es da eine Lösung?

    Danke und Grüße
    Klaus

Antwort hinterlassen