javascript 如何检测 iframe 何时已加载
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17158932/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to detect when an iframe has already been loaded
提问by B T
It seems that $('#someIframe').load(function(){...})
won't fire if it is attached after the iframe has finished loading. Is that correct?
$('#someIframe').load(function(){...})
如果在 iframe 加载完成后附加它,似乎不会触发。那是对的吗?
What I'd really like is to have a function that is always called once when or after an iframe has loaded. To make this clearer, here are two cases:
我真正想要的是有一个函数,它总是在 iframe 加载时或之后调用一次。为了更清楚地说明这一点,这里有两种情况:
- Iframe hasn't loaded yet: run a callback function once it loads.
- Iframe has already loaded: run the callback immediately.
- iframe 尚未加载:加载后运行回调函数。
- iframe 已经加载:立即运行回调。
How can I do this?
我怎样才能做到这一点?
回答by dude
I've banged my head against a wall until I found out what's happening here.
我一直用头撞墙,直到我发现这里发生了什么。
Background information
背景资料
- Using
.load()
isn't possible if the iframe has already been loaded (event will never fire) - Using
.ready()
on an iframe element isn't supported (reference) and will call the callback immediately even if the iframe isn't loaded yet - Using
postMessage
or a calling a container function onload
inside the iframe is only possible when having control over it - Using
$(window).load()
on the container would also wait for other assets to load, like images and other iframes. This is not a solution if you want to wait only for a specific iframe - Checking
readyState
in Chrome for an alredy fired onload event is meaningless, as Chrome initializes every iframe with an "about:blank" empty page. ThereadyState
of this page may becomplete
, but it's not thereadyState
of the page you expect (src
attribute).
.load()
如果 iframe 已经加载,则无法使用(事件永远不会触发).ready()
不支持在 iframe 元素上使用(参考),即使 iframe 尚未加载,也会立即调用回调- 在 iframe 内部使用
postMessage
或调用容器函数load
只有在控制它时才有可能 $(window).load()
在容器上使用也会等待其他资产加载,如图像和其他 iframe。如果您只想等待特定的 iframe,这不是解决方案readyState
在 Chrome 中检查已触发的 onload 事件是没有意义的,因为 Chrome 使用“about:blank”空页面初始化每个 iframe。在readyState
此页面的可能complete
,但它不是readyState
你所希望的网页(的src
属性)。
Solution
解决方案
The following is necessary:
以下是必要的:
- If the iframe is not loaded yet we can observe the
.load()
event - If the iframe has been loaded already we need to check the
readyState
- If the
readyState
iscomplete
, we can normally assume that the iframe has already been loaded. However, because of the above-named behavior of Chrome we furthermore need to check if it's thereadyState
of an empty page - If so, we need to observe the
readyState
in an interval to check if the actual document (related to the src attribute) iscomplete
- 如果 iframe 尚未加载,我们可以观察
.load()
事件 - 如果 iframe 已经加载,我们需要检查
readyState
- 如果
readyState
是complete
,我们通常可以假设 iframe 已经加载。但是,由于 Chrome 的上述行为,我们还需要检查它是否readyState
为空页面 - 如果是这样,我们需要
readyState
在一个时间间隔内观察以检查实际文档(与 src 属性相关)是否是complete
I've solved this with the following function. It has been (transpiled to ES5) successfully tested in
我已经用以下函数解决了这个问题。它已(转换为 ES5)在
- Chrome 49
- Safari 5
- Firefox 45
- IE 8, 9, 10, 11
- Edge 24
- iOS 8.0 ("Safari Mobile")
- Android 4.0 ("Browser")
- 铬 49
- 野生动物园 5
- 火狐 45
- IE 8、9、10、11
- 边缘 24
- iOS 8.0(“Safari 移动版”)
- Android 4.0(“浏览器”)
Function taken from jquery.mark
取自jquery.mark 的函数
/**
* Will wait for an iframe to be ready
* for DOM manipulation. Just listening for
* the load event will only work if the iframe
* is not already loaded. If so, it is necessary
* to observe the readyState. The issue here is
* that Chrome will initialize iframes with
* "about:blank" and set its readyState to complete.
* So it is furthermore necessary to check if it's
* the readyState of the target document property.
* Errors that may occur when trying to access the iframe
* (Same-Origin-Policy) will be catched and the error
* function will be called.
* @param {jquery} $i - The jQuery iframe element
* @param {function} successFn - The callback on success. Will
* receive the jQuery contents of the iframe as a parameter
* @param {function} errorFn - The callback on error
*/
var onIframeReady = function($i, successFn, errorFn) {
try {
const iCon = $i.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $i.contents();
if($con.length === 0) { // https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch(e) { // accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$i.on("load.jqueryMark", () => {
try {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href !== bl || src === bl || src === "") {
$i.off("load.jqueryMark");
callCallback();
}
} catch(e) {
errorFn();
}
});
};
if(iCon.document.readyState === compl) {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch(e) { // accessing contentWindow failed
errorFn();
}
};
Working example
工作示例
Consisting of two files (index.html and iframe.html): index.html:
由两个文件(index.html 和 iframe.html)组成: index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Parent</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
<script>
$(function() {
/**
* Will wait for an iframe to be ready
* for DOM manipulation. Just listening for
* the load event will only work if the iframe
* is not already loaded. If so, it is necessary
* to observe the readyState. The issue here is
* that Chrome will initialize iframes with
* "about:blank" and set its readyState to complete.
* So it is furthermore necessary to check if it's
* the readyState of the target document property.
* Errors that may occur when trying to access the iframe
* (Same-Origin-Policy) will be catched and the error
* function will be called.
* @param {jquery} $i - The jQuery iframe element
* @param {function} successFn - The callback on success. Will
* receive the jQuery contents of the iframe as a parameter
* @param {function} errorFn - The callback on error
*/
var onIframeReady = function($i, successFn, errorFn) {
try {
const iCon = $i.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $i.contents();
if($con.length === 0) { // https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch(e) { // accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$i.on("load.jqueryMark", () => {
try {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href !== bl || src === bl || src === "") {
$i.off("load.jqueryMark");
callCallback();
}
} catch(e) {
errorFn();
}
});
};
if(iCon.document.readyState === compl) {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch(e) { // accessing contentWindow failed
errorFn();
}
};
var $iframe = $("iframe");
onIframeReady($iframe, function($contents) {
console.log("Ready to got");
console.log($contents.find("*"));
}, function() {
console.log("Can not access iframe");
});
});
</script>
<iframe src="iframe.html"></iframe>
</body>
</html>
iframe.html:
iframe.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Child</title>
</head>
<body>
<p>Lorem ipsum</p>
</body>
</html>
You can also change the src
attribute inside index.html
to e.g. "http://example.com/". Just play around with it.
您还可以src
将里面的属性更改index.html
为例如“ http://example.com/”。只是玩弄它。
回答by Adam Bergmark
I'd use postMessage. The iframe can assign its own onload event and post to the parent. If there are timing issues just make sure to assign the parent's postMessage handler before creating the iframe.
我会使用postMessage。iframe 可以分配自己的 onload 事件并发布到父级。如果存在时间问题,请确保在创建 iframe 之前分配父级的 postMessage 处理程序。
For this to work the iframe must know the url of the parent, for instance by passing a GET parameter to the iframe.
为此,iframe 必须知道父级的 url,例如通过将 GET 参数传递给 iframe。
回答by Mahyar Zarifkar
I had the same problem. In my case, I simply checked if the onload
function is fired or not.
我有同样的问题。就我而言,我只是检查了该onload
函数是否被触发。
var iframe = document.getElementById("someIframe");
var loadingStatus = true;
iframe.onload = function () {
loadingStatus = false;
//do whatever you want [in my case I wants to trigger postMessage]
};
if (loadingStatus)
//do whatever you want [in my case I wants to trigger postMessage]
回答by B T
I tried very hard to come to a solution that worked consistently cross browser. IMPORTANT: I was not able to come to such a solution. But here is as far as I got:
我非常努力地想找到一个在跨浏览器中始终有效的解决方案。重要提示:我无法找到这样的解决方案。但据我所知:
// runs a function after an iframe node's content has loaded
// note, this almost certainly won't work for frames loaded from a different domain
// secondary note - this doesn't seem to work for chrome : (
// another note - doesn't seem to work for nodes created dynamically for some reason
function onReady(iframeNode, f) {
var windowDocument = iframeNode[0].contentWindow.document;
var iframeDocument = windowDocument?windowDocument : iframeNode[0].contentWindow.document;
if(iframeDocument.readyState === 'complete') {
f();
} else {
iframeNode.load(function() {
var i = setInterval(function() {
if(iframeDocument.readyState === 'complete') {
f();
clearInterval(i);
}
}, 10);
});
}
}
and I was using it like this:
我是这样使用它的:
onReady($("#theIframe"), function() {
try {
var context = modal[0].contentWindow;
var i = setInterval(function() {
if(context.Utils !== undefined && context.$) { // this mess is to attempt to get it to work in firefox
context.$(function() {
var modalHeight = context.someInnerJavascript();
clearInterval(i);
});
}
}, 10);
} catch(e) { // ignore
console.log(e);
}
});
Note that even thisdoes not solve the problem for me. Here are some problems with this solution:
请注意,即使这样也不能解决我的问题。以下是此解决方案的一些问题:
- In onReady, for iframes that were added dynamically, iframeDocument.readyState seems to be stuck at "uninitialized" and thus the callback never fires
- The whole setup still doesn't seem to work in firefox for some reason. It almost seems like the setInterval function is cleared externally.
- Note that some of these problems only happen when there is a lot of other stuff loading on the page, which makes the timing of these things less deterministic.
- 在 onReady 中,对于动态添加的 iframe, iframeDocument.readyState 似乎卡在“未初始化”状态,因此回调永远不会触发
- 由于某种原因,整个设置似乎仍然无法在 Firefox 中工作。似乎 setInterval 函数是从外部清除的。
- 请注意,其中一些问题仅在页面上加载大量其他内容时才会发生,这使得这些事情的时间确定性较低。
So if anyone can improve upon this, it would be much appreciated.
因此,如果有人可以对此进行改进,将不胜感激。
回答by Onur Topal
I think you should try using onreadystatechange
event.
我认为您应该尝试使用onreadystatechange
事件。
$(function () {
var innerDoc = ($("#if")[0].contentDocument) ? $("#if")[0].contentDocument : $("#if")[0].contentWindow.document;
console.debug(innerDoc);
$("#if").load( function () {
alert("load");
alert(innerDoc.readyState)
});
innerDoc.onreadystatechange = function () {
alert(innerDoc.readyState)
};
setTimeout(innerDoc.onreadystatechange, 5000);
});
EDIT: the context is not what I think it is. you can just check the readyState of iframe document and everything should be fine.
编辑:上下文不是我认为的那样。你可以检查 iframe 文档的 readyState ,一切都应该没问题。
OP: This is a packaged up function I made from the concepts described above:
OP:这是我根据上述概念制作的打包函数:
// runs a function after an iframe node's content has loaded
// note, this almost certainly won't work for frames loaded from a different domain
onReady: function(iframeNode, f) {
var windowDocument = iframeNode[0].contentWindow.document;
var iframeDocument = windowDocument?windowDocument : iframeNode[0].contentWindow.document
if(iframeDocument.readyState === 'complete') {
f();
} else {
iframeNode.load(f);
}
}
回答by raam86
Only when the content inside the iframe is loaded innerDoc is true and fires code inside the if.
仅当加载 iframe 内的内容时,innerDoc 为 true 并触发 if 内的代码。
window.onload = function(){
function manipulateIframe(iframeId, callback) {
var iframe = document.getElementById(iframeId).contentWindow.document;
callback(iframe);
};
manipulateIframe('IFwinEdit_forms_dr4r3_forms_1371601293572', function (iframe) {
console.log(iframe.body);
});};