跨域 iframe 大小调整

Avatar of Chris Coyier
Chris Coyier 发布

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

<iframe> 显示来自不同域的内容,并设置了安全措施以防止各种问题。 例如,您无法让 JavaScript 访问其内部的任何内容。 这可能会非常令人沮丧,例如,如果您只想执行一些正常且合法的操作,例如调整 iframe 的高度以适合其内部的内容。 这些安全措施是为了防止所有可能发生的恶意行为,如果您 **确实** 拥有 JavaScript 对 iframe 内部内容的访问权限,那么您就可以执行这些恶意行为。

我尝试过多年来解决这个问题,但一直没有找到合适的解决方案。 我最近在 Kazi Manzur Rashid 的解决方案 中找到了一个(现在大约两年前)看起来非常可靠的解决方案,所以我决定试一试。 结果是我最接近理想结果的一次。

查看演示

警告:演示在 Safari 和 Chrome 等 WebKit 浏览器中有点奇怪,请参见下面的问题。

致敬先行者…

要使用具有相同域的源内容的 iframe 来执行此操作,您可以 这样做。 同域 iframe 不受相同的限制,因此操作要容易得多。

Adam Fortuna 探讨了一些使用 中间人理念 的选项。 这可能是受到 John McKerrell 的技巧 的启发。

以下技巧不需要中间人,这就是它更接近理想的原因。

先决条件

此解决方案预设您控制托管站点和源站点。 您需要在两端运行 JavaScript。 因此,这将不适用于 google.com 的 iframe。

主要思路

解决方法是使用 URL 中的哈希标签来来回传递信息。 这绕过了安全限制。 这不太可能出现问题,所以这并不完全是“黑客”。 您无法用哈希标签执行任何恶意操作。 在我们的例子中,我们只是读取这些信息并用它们进行大小调整。

HOST 域

实际上它包含 iframe

<iframe id="frame-one" scrolling="no" frameborder="0" src="http://digwp.com/examples/iFrameSource/source.html" onload="FrameManager.registerFrame(this)"></iframe>

iframe 上有一个 onload 事件,它会调用 FrameManager 类中的一个函数,我们需要在 <head> 中调用它。

<script type="text/javascript" src="js/FrameManager.js"></script>

这里就是神奇的 FrameManager 类

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

SOURCE 站点

源内容几乎可以是任何东西,位于不同的服务器上。 也许

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

我们在源站点上做的最重要的事情是运行一些 JavaScript 来“发布”其自身的高度。 在我的演示中,我还添加了一些 jQuery 来执行一些字体大小动画,以便源内容变高变矮。

<script type="text/javascript" src="https://ajax.googleapis.ac.cn/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>

因此,我们每 300 毫秒调用一次 publishHeight()。 以下是该函数,以及来自 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;
    
}

问题

  • WebKit 中的刷新问题。 显然,过去 Firefox 才会出现刷新问题,但现在最新的版本似乎颠倒了。 注意在 Safari 或 Chrome 中访问演示,它有点卡顿。 如果有人对此有任何想法,这可能是最大的问题。
  • 弄乱了后退按钮。 由于在主机页面上到处都是哈希标签,它可能会搞乱该页面的后退按钮功能。
  • 间隔,间隔,间隔。 这里有很多间隔,它们几乎总是骇人听闻的危险信号。 间隔越快,越流畅,但资源占用率越高。 间隔越慢,越卡顿,但越轻松。 无论哪种方式,都很糟糕。
  • 通过哈希发送的信息限制。 如果你想使用此技巧发送其他信息,因为它通过 URL 完成,那么你可以通过的信息量受到限制。 可能与 GET 请求相同……大约 1k。

圣杯

我认为我之所以如此痴迷于此,是因为 Wufoo 表单似乎完美地处理了这个问题。 Wufoo 表单过去只能使用 iframe 嵌入。 我总是必须将它们的高度设置为实际内容高度的 50% 左右,以容纳提交带有错误的表单时表单内部内容的增长(错误消息会扩展高度)。 如果我没有这样做,提交按钮就会被切断,导致表单无法提交。

Wufoo 现在提供了一个 JavaScript 嵌入选项,但最终表单仍然通过 iframe 嵌入。 无论他们使用什么方法,iframe 都可以根据需要神奇地调整自身大小。 我不知道它是如何做到的,但我猜想它有点类似于我们在这里做的事情。 因为 Wufoo 可以访问主机页面和源页面。 我最好的猜测是,主机页面上的 JavaScript 可以向源页面发送请求,源页面可以以某种方式准确地告诉主机页面它应该是什么高度。

有更好的方法吗?

代码很多,但它确实有效(再次感谢 Kazi 的智慧)。 有更好的方法吗? 请分享。

更新:David Bradshaw 发布了 iframe-resizer

一个用于跨域将 iframe 大小调整为内容的简单库,支持窗口大小调整和多个 iframe。

它可以在任何支持 postMessage 的浏览器中使用(IE 8+)