iframe自动调整高度以适应内容变化

27
我有一个iframe,你可以在下面的链接中看到。

http://one2onecars.com

iframe是屏幕中央的在线预订。我遇到的问题是,尽管iframe的高度在页面加载时是可以的,但我需要它在页面内容调整时自动调整高度。例如,如果我在在线预订中进行邮政编码搜索,它会创建一个下拉菜单,然后使“下一步”按钮不可见。

我希望的是,当在线预订的内容发生变化时,iframe会自动调整到新的高度(动态调整),因为它不会加载其他页面。

我尝试了几种使用jquery解决此问题的不同脚本,但它们似乎只在页面首次加载时自动调整iframe的高度,而不是在iframe的内容发生变化时。

这种做法是否可行?

我目前的代码是固定高度的:

        <div id="main-online-booking">

            <iframe id="main-online-frame" class="booking-dimensions" src="http://www.marandy.com/one2oneob/login-guest.php" scrolling="no" frameborder="0"></iframe>

        </div>

#main-online-booking {
    height: 488px;
    border-bottom: 6px #939393 solid;
    border-left: 6px #939393 solid;
    border-right: 6px #939393 solid;
    z-index: 4;
    background-color: #fff;
}


.booking-dimensions {
    width: 620px;
    height: 488px;
}

如果有人能帮我解决这个问题,我将非常感激!

请看这个解决方案:https://dev59.com/VmXWa4cB1Zd3GeqPOJGA#14246632 - algorhythm
为什么要首选使用Iframe?只需要很少的代码就可以将Iframe模块转换为AJAX。 - charlietfl
由于在线预订是托管在我们的服务器上而不是客户端,因此... - nsilva
1
如果Iframe在另一个域上,由于安全限制,无法在框架和主文档之间使用脚本。 - charlietfl
1
不同的域使得AJAX更具吸引力...使用jsonp来传输数据。 - charlietfl
这是我想出来的代码: var height = 0; $('iframe').contents().filter(function() { if($(this).height() > height) height = $(this).height(); }); $('iframe').css('height', height +'px'); - Jiskiras
5个回答

32
现代浏览器具有一些新功能,使得这个任务比以前更容易。
PostMessage postMessage API提供了一种简单的方法,用于在iFrame和其父级之间进行通信。
要向父页面发送消息,请按以下方式调用它。
parent.postMessage('Hello parent','http://origin-domain.com');

在另一个方向上,我们可以使用以下代码将消息发送到iFrame。
var iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage('Hello my child', 'http://remote-domain.com:8080');

要接收消息,请为消息事件创建一个事件监听器。
function receiveMessage(event)
{
  if (event.origin !== "http://remote-domain.com:8080")
    return;

  console.log(event.data);
}

if ('addEventListener' in window){
    window.addEventListener('message', receiveMessage, false);
} else if ('attachEvent' in window){ //IE
    window.attachEvent('onmessage', receiveMessage);

这些示例使用origin属性来限制消息发送的位置,并检查它来自哪里。可以使用*来允许发送到任何域,并且在某些情况下,您可能希望接受来自任何域的消息。然而,如果这样做,您需要考虑安全性问题,并对传入的消息进行自己的检查,以确保它包含您期望的内容。在这种情况下,iframe可以将其高度发布到'*',因为我们可能有多个父域。然而,最好检查传入的消息是否来自iFrame。
function isMessageFromIFrame(event,iframe){
    var
        origin  = event.origin,
        src     = iframe.src;

    if ((''+origin !== 'null') && (origin !== src.substr(0,origin.length))) {
        throw new Error(
            'Unexpect message received from: ' + origin +
            ' for ' + iframe.id + '. Message was: ' + event.data  
        );
    }

    return true;
}

MutationObserver

在更现代的浏览器中,另一个进步是MutationObserver,它允许您监视DOM中的更改;因此,现在可以检测可能影响iFrame大小的更改,而无需使用setInterval进行持续轮询。

function createMutationObserver(){
    var
        target = document.querySelector('body'),

        config = {
            attributes            : true,
            attributeOldValue     : false,
            characterData         : true,
            characterDataOldValue : false,
            childList             : true,
            subtree               : true
        },

        observer = new MutationObserver(function(mutations) {
            parent.postMessage('[iframeResize]'+document.body.offsetHeight,'*');
        });

    log('Setup MutationObserver');
    observer.observe(target, config);
}

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

if (MutationObserver){
    createMutationObserver();
}

计算准确的高度

获取iFrame的准确高度并不像应该那么简单,因为你有六个不同的属性可以检查,但没有一个能给出始终正确的答案。我想到的最好的解决方案是使用这个函数,只要你不使用CSS来溢出body标签。

function getIFrameHeight(){
    function getComputedBodyStyle(prop) {
        const style = document.defaultView.getComputedStyle(
            document.body, null);
        return parseInt(style.getPropertyValue(prop), 10);
    }

    return document.body.offsetHeight +
        getComputedBodyStyle('marginTop') +
        getComputedBodyStyle('marginBottom');
}

这是IE9版本,如果需要更长的IE8版本,请查看此链接answer
如果您的代码溢出了页面主体,并且无法修复,那么使用document.documentElement的offsetHeight或scrollHeight属性是您的最佳选择。两者都有优缺点,最好测试一下,看哪个适合您。
其他问题
其他需要考虑的事项包括:页面上有多个iFrame,CSS :Checkbox和:Hover事件导致页面调整大小,避免在iFrames的body和html标签中使用height auto,以及窗口调整大小。
IFrame Resizer库
我已经将所有这些内容封装在一个简单的无依赖库中,还提供了一些额外的功能和页面大小检测算法,这里没有讨论。

https://github.com/davidjbradshaw/iframe-resizer

这在所有现代浏览器中都有效。

1
在函数 getComputedBodyStyle 中,您定义了一个参数 prop,但在函数体中没有使用它。是否有拼写错误? - Geert
2
天啊,感谢你提供的调整大小库。它非常容易设置并且完美地工作。 - Jimmy
1
这可能是获取高度的更好方法: document.body.getBoundingClientRect().height - Eddie
1
我测试了@Eddie发布的方法,似乎可以互换使用,但有一个棘手的问题是,无论哪种方法都无法获取当前不可见的iframe的有效高度。例如,Discourse提供的嵌入代码使用这种方法自动调整其iframe以适应嵌入的讨论。因此,如果讨论嵌入在选项卡界面中,当加载时,如果它不是当前选定的选项卡,则无法正确调整大小。因此,我们最终不得不在切换到该选项卡时强制删除并重新初始化Discourse iframe。 - undefined
1
选项卡一直是最大的挑战。这也是为什么 iframe-resizer API 中有一个手动方法来强制调整大小的主要原因。 - undefined
显示剩余2条评论

31

setInterval

使用 setInterval 并自行监测 iframe 的内容是实现此目的的唯一向后兼容的方法(由于浏览器技术的进步,参见 David Bradshaw 的回答)。当内容更改其高度时,您需要更新 iframe 的尺寸。很不幸,没有任何事件可以监听以使其变得容易。

以下是一个基本示例,它仅在 iframe 内容所更改的大小是主页面流的一部分时才起作用。如果元素被浮动或定位,则必须针对它们来查找高度更改。

jQuery(function($){
  var lastHeight = 0, curHeight = 0, $frame = $('iframe:eq(0)');
  setInterval(function(){
    curHeight = $frame.contents().find('body').height();
    if ( curHeight != lastHeight ) {
      $frame.css('height', (lastHeight = curHeight) + 'px' );
    }
  },500);
});

显然,根据您的需求,您可以修改此代码的视角,使其从iframe上自行运行,而不是期望成为主页面的一部分。

跨域问题

您将发现的问题是,由于浏览器安全性,如果iframe与主页面位于不同的主机上,则它不会允许您访问iframe的内容,因此除非您有一种方法向在iframe中显示的html添加任何脚本,否则实际上没有什么可以做的。

AJAX

由于一些人建议尝试通过AJAX使用第三方服务,但除非该服务支持此方法,否则很可能无法使其工作 - 尤其是如果它是一个需要在https / ssl上运行的预订服务。

由于似乎您完全控制iframe内容,因此您拥有完全的选项,包括使用JSONP的AJAX。但是,需要注意的是,如果您的预订系统是多步骤的,则需要确保您拥有设计良好的UI - 以及可能需要一些历史记录/片段管理代码 - 如果您要走AJAX路线。所有这些都是因为您永远无法知道用户何时决定在浏览器中前进或后退(其中iframe将在合理范围内自动处理)。良好设计的UI可以使用户不进行此操作。

跨域通信

如果您控制双方(听起来像是),则还可以使用window.postMessage进行跨域通信 - 有关更多信息,请参见https://developer.mozilla.org/en-US/docs/DOM/window.postMessage


关于 AJAX 的最后一句话是荒谬的。OP 提供服务,绝对没有理由他们不能提供一个 jsonp API 来实现同样的功能。 - charlietfl
@charlietfl,这个问题中哪里提到了OP提供iframe内容,怎么会荒谬呢?我承认我错过了后来的评论,但问题并没有提到它,如果处理第三方,我所说的完全正确(尤其是我不断提到第三方)。然而,你说得好,我会相应地修改我的答案。不过,我不知道你在第二条评论中在说什么... - Pebbl
@DavidBradshaw ~ 其他人拒绝了您的编辑,因为如果涉及到相当不同的内容,您应该将其作为新答案发布。如果您这样做,我很乐意为您的补充点赞,因为它们清晰且更新(至少适用于现代浏览器)。 - Pebbl
@pebbl,好的,我已经写了一个更长的答案。 - David Bradshaw
@DavidBradshaw ~ 很好的整合,我点赞 +1。 - Pebbl
显示剩余2条评论

7
我写了这个脚本,对于我来说它完美地运行着。请随意使用!
function ResizeIframeFromParent(id) {
    if (jQuery('#'+id).length > 0) {
        var window = document.getElementById(id).contentWindow;
        var prevheight = jQuery('#'+id).attr('height');
        var newheight = Math.max( window.document.body.scrollHeight, window.document.body.offsetHeight, window.document.documentElement.clientHeight, window.document.documentElement.scrollHeight, window.document.documentElement.offsetHeight );
        if (newheight != prevheight && newheight > 0) {
            jQuery('#'+id).attr('height', newheight);
            console.log("Adjusting iframe height for "+id+": " +prevheight+"px => "+newheight+"px");
        }
    }
}

你可以在循环中调用该函数:
<script>
jQuery(document).ready(function() {
    // Try to change the iframe size every 2 seconds
    setInterval(function() {
        ResizeIframeFromParent('iframeid');
    }, 2000);
});
</script>

2
ResizeObserver允许您的代码保持封装在iframe内部,与外部范围解耦(即与postMessage相比),并且比通用的MutationObserver更轻量级。
下面是一个简单的示例(或者可以参考Mozilla):
const myElement = document.getElementById('my-element');
const resizeObserver = new ResizeObserver((entries) => {
  const dims = myElement.getBoundingClientRect(); // or see Mozilla for `entries` example
  console.log(`new height (${dims.height}) and width (${dims.width})`);
});
resizeObserver.observe(myElement);

0

使用这个脚本:

$(document).ready(function () {
  // Set specific variable to represent all iframe tags.
  var iFrames = document.getElementsByTagName('iframe');

  // Resize heights.
  function iResize() {
    // Iterate through all iframes in the page.
    for (var i = 0, j = iFrames.length; i < j; i++) {
    // Set inline style to equal the body height of the iframed content.
      iFrames[i].style.height = iFrames[i].contentWindow.document.body.offsetHeight + 'px';
    }
  }

 // Check if browser is Safari or Opera.
 if ($.browser.safari || $.browser.opera) {
    // Start timer when loaded.
    $('iframe').load(function () {
    setTimeout(iResize, 0);
    });

   // Safari and Opera need a kick-start.
   for (var i = 0, j = iFrames.length; i < j; i++) {
     var iSource = iFrames[i].src;
     iFrames[i].src = '';
     iFrames[i].src = iSource;
   }
 } else {
    // For other good browsers.
    $('iframe').load(function () {
    // Set inline style to equal the body height of the iframed content.
    this.style.height = this.contentWindow.document.body.offsetHeight + 'px';
    });
  }
});

注意:请在 Web 服务器上使用。


请注意,此代码仅适用于相同域。似乎OP想在其他域上使用Iframe。 - charlietfl
这样可以在同一域名下的 iframe 上工作吗?如果是这种情况,我将不得不让客户将域名转移到我们这里。 - nsilva
1
@nsilva 这段代码只能在页面加载时运行,因为据我所见它没有使用任何轮询方法。 - Pebbl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接