How to detect when an iframe has already been loaded
在iframe完成加载后,如果将
我真正想要的是拥有一个在加载iframe时或加载后始终被调用一次的函数。 为了更清楚一点,这是两种情况:
- iframe尚未加载:加载后运行回调函数。
- iframe已加载:请立即运行回调。
我怎样才能做到这一点?
我把头撞在墙上,直到发现这里发生了什么。
背景资料
-
如果iframe已加载,则无法使用
.load() (事件将永远不会触发) -
不支持(参考)在iframe元素上使用
.ready() ,即使尚未加载iframe,也会立即调用回调 -
仅在对iframe进行控制时,才可以使用
postMessage 或在iframe中的load 上调用容器函数 -
在容器上使用
$(window).load() 还将等待其他资源加载,例如图像和其他iframe。如果您只想等待特定的iframe,这不是解决方案 -
在Chrome中检查
readyState 是否存在触发的onload事件是没有意义的,因为Chrome会使用" about:blank"空白页初始化每个iframe。此页面的readyState 可能是complete ,但不是您期望的页面的readyState (src 属性)。
解
以下是必要的:
我已经通过以下功能解决了这个问题。它已(已转换为ES5)在以下环境中成功测试
- 铬49
- Safari 5
- 火狐45
- IE 8、9、10、11
- 边缘24
- iOS 8.0(" Safari移动版")
- Android 4.0("浏览器")
函数取自jquery.mark
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | /** * 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(); } }; |
工作实例
由两个文件(index.html和iframe.html)组成:
index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | <!doctype html> <html> <head> <meta charset="utf-8"> Parent </head> <body> <script src="https://code.jquery.com/jquery-1.12.2.min.js"> $(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"); }); }); <iframe src="iframe.html"></iframe> </body> </html> |
iframe.html:
1 2 3 4 5 6 7 8 9 10 11 12 | <!doctype html> <html> <head> <meta charset="utf-8"> Child </head> <body> <p><center>[wp_ad_camp_3]</center></p><p> Lorem ipsum </p> </body> </html> |
您还可以将
我会使用postMessage。 iframe可以分配自己的onload事件并发布到父级。如果存在计时问题,请确保在创建iframe之前先分配父级的postMessage处理程序。
为此,iframe必须知道父级的网址,例如通过将GET参数传递给iframe。
我有同样的问题。就我而言,我只是检查了
1 2 3 4 5 6 7 8 | 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] |
我非常努力地提出了一种在跨浏览器中始终有效的解决方案。重要信息:我无法提出这样的解决方案。但是据我所知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 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); }); } } |
我像这样使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); } }); |
请注意,即使这样也不能解决我的问题。以下是此解决方案的一些问题:
- 在onReady中,对于动态添加的iframe,iframeDocument.readyState似乎停留在"未初始化"状态,因此回调永远不会触发
- 由于某些原因,整个设置似乎仍无法在Firefox中使用。似乎setInterval函数在外部已清除。
- 请注意,其中一些问题仅在页面上加载了许多其他内容时才会发生,这使得这些事情的时间确定性较低。
因此,如果有人可以对此进行改进,将不胜感激。
我认为您应该尝试使用
http://jsfiddle.net/fk8fc/3/
1 2 3 4 5 6 7 8 9 10 11 12 13 | $(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); }); |
编辑:上下文不是我认为的那样。您只需检查iframe文档的readyState,一切就可以了。
OP:这是我根据上述概念制作的打包功能:
1 2 3 4 5 6 7 8 9 10 11 | // 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); } } |
仅当加载iframe中的内容innerDoc时为true,并在if内部触发代码。
1 2 3 4 5 6 7 8 | 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); });}; |
例