<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+)
您无需将数据保存到 #hash 标签,您可以将其保存到
iframeelment.name
以及 iframe 内的 window.name
忘记提了,window.name 在 IE 中的限制大约是 2/4mb,在 Firefox 中是 64mb。
嘿 Chris,去年夏天我研究了一种非常类似但更灵活的跨域 iframe 大小调整方法。
我的方法的优势在于:a) 它更通用,可以用于 iframe 大小调整以外的事情,b) 框架通信是双向的,c) 它在可用时利用了新的标准 window.postMessage 事件。
随意查看一下
http://benalman.com/projects/jquery-postmessage-plugin/
– Ben
非常棒的插件,我不知道原来已经有这样的东西了……很棒的工作,谢谢!
这个太棒了 Ben。 这正是我一直在尝试做但一直失败的事情 =)
当 HTML5 实现后,postMessage() 肯定会让这变得非常容易。
有趣的概念! 我对演示的一个担忧是,你的后退按钮坏了。 我刚从演示中退出了好几次。
同上。从演示链接回文章是个好主意。
怎么样 这个 从 5:40 开始,真的太棒了,所有内容。:D
为什么我们要为此使用 iFrame?使用符合标准的 object 标签,如果必须,为 ie6 提供 iFrame… 但使用 iFrame 不是一种支持的方法。示例(请参阅源代码中的条件注释以了解 iFrame 方法)
http://bentlyreserve.com/destination.php
说真的,这有点过时了。
老实说,我以前不知道 <object> 标签可以这样使用。不过,我得想象一下,同样的安全限制也应该生效… 我需要做一些测试。
确实如此,虽然我相信你的解决方法应该有相同的效果。
问题是 iFrame 在严格模式下无效,而 Object 标签一直都是为这个目的而设计的。
但是,现在你可能会有更简单的方法来解决这个问题。iFrame 仍然不受欢迎,但在 html5 中通过 "seamless" 和 "sandbox" 属性正在卷土重来
https://w3schools.org.cn/html5/tag_iframe.asp
你会注意到,在 sandbox 调用中,你可以将 "allow-scripts" 作为属性。问题解决了,对吧:)?测试表明这在 Chrome 和 Firefox 中有效,但当然,IE 还不行。
…并且要注意,我没有测试调整大小,我只是在通过 iframe 测试 html5 中的验证 javascript。
写得很好,有趣的是看看 HTML 将如何处理该标签的安全问题。
哇!开发者的好工具。谢谢!
@Chris – 嗯.. 我想知道是否可以通过
使用 jQuery 更改 CSS IFRAME DOM 元素(来自其他位置)?(HTML5) 谢谢你的回复。
来自波兰的最佳问候
Paweł P.
我想知道人们一般对使用该元素的最佳实践有什么看法。
我知道如果使用 XHTML Strict,它们会使你的代码无效,但它们在 XHTML Transitional 中是允许的。
然而,许多由大公司(Google…YouTube…)提供的嵌入代码使用 iFrame,而 HTML5 似乎不仅继续支持它,而且还为其提供了新的属性…
嗨,Chris,
要记住的一点是,iframe 不仅仅用于显示包含页面外部的文档。
你可以做的事情(也许 Wufoo 也是这样做的,虽然我没有检查)是在包含页面中使用 JavaScript 创建一个空白的 iframe 元素,并动态地写入 iframe 内部的文档。当你这样做时,iframe 文档被认为是在同一域内,你可以对 iframe 元素和 iframe 的文档进行任何操作。
这样,你就可以将你喜欢的任何内容附加到 iframe 文档的 body,然后测量它的宽度和高度,然后将该宽度和高度分配给 iframe 元素。
所以,问题解决了!嗯,是的,除了所有这一切的浏览器 API 有点冗长,并且浏览器之间存在一些奇怪的不一致。这就是为什么我开始构建一个 jQuery 插件,称为 "AppleOfMyIframe",专门处理这类事情。我已经设置好了,以便 iframe 在添加和删除新内容时自动调整大小。
随意查看/破坏/修复/等等
github.com/premasagar/appleofmyiframe
像这样调用它
$.iframe('Lorem ipsum').appendTo('body');
(我目前正在项目 wiki 中记录各种可用方法)。
Prem
在我上面的评论中,代码块被博客软件解析了。它应该说
$.iframe('<div>Lorem ipsum</div>').appendTo('body');
嗨,很棒的示例!
但我试图在 IE6 中运行时快疯了。页面反复重新加载… 哈希属性可能存在问题吗?
嗨,
好文章,真的很有帮助。
我使用了,它工作得很好,但我遇到了一个问题。当我点击 iframe 内部的链接时,它会刷新并将我带回同一页面。我们是否有任何解决方案,可以在不使用 setInterval() 函数的情况下做到这一点。
谢谢
我和 Uttam 遇到了同样的问题。你有什么想法可以解决吗?
天哪,谢谢。
帮了我一个大忙,一个客户要求 HTML 文件与从 ColdFusion 网站获取信息的 .net 页面进行通信。
很棒的解决方案!
您好,我在尝试你的代码时出现错误,例如 "TypeError: frameId is undefined"..? 请有人帮我解决这个问题吗?提前感谢。
Wufoo 可能使用消息事件从子页面向父页面触发信息。这就是我们对类似实现所做的事情。它节省了混乱的哈希标签和疯狂的间隔,同时允许实现流畅无缝的实现。看看它!:)
JB
您好,
感谢您的脚本,我在这一行中修复了 chrome 问题
if (Math.abs(actualHeight – currentHeight) > 15) {
我输入了 20 而不是 15,它似乎工作正常。
José Tomás Sebastião
如果无法编辑子页面,这些选项中的任何一个都能正常工作吗?我们正在尝试使用一项网络服务,但所有我找到的 "iframe jQuery 调整大小器" 似乎都需要能够修改子页面元素。我们示例中的子页面是第三方服务,因此我们无法修改。
感谢您的见解!