Cross Domain iframe Resizing

Avatar of Chris Coyier
Chris Coyier on

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

<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.

Demo ansehen

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+).