如何检测何时已加载iframe

时间:2013-06-18 00:31:34

标签: javascript jquery dom

如果在iframe完成加载后附加$('#someIframe').load(function(){...}),则{{1}}似乎不会触发。那是对的吗?

我真正喜欢的是拥有一个在加载iframe时或之后始终调用一次的函数。为了更清楚,这里有两种情况:

  • 尚未加载iframe :加载后运行回调函数。
  • iframe已加载:立即运行回调。

我该怎么做?

6 个答案:

答案 0 :(得分:33)

在我发现这里发生的事情之前,我一直撞到了墙上。

背景资料

  • 如果iframe已经加载(事件永远不会触发),则无法使用.load()
  • 不支持在iframe元素上使用.ready()reference),即使iframe尚未加载,也会立即调用回调
  • 只有在控制iframe时才能使用postMessage或在iframe内load调用容器功能
  • 在容器上使用$(window).load()也会等待加载其他资源,例如图片和其他iframe。如果您只想等待特定的iframe
  • ,这不是解决方案
  • 在Chrome中检查readyState以查找alredy已启动的onload事件是没有意义的,因为Chrome会使用“about:blank”空白页初始化每个iframe。此页面的readyState可能是complete,但它不是您期望的页面readyStatesrc属性)。

解决方案

以下是必要的:

  1. 如果尚未加载iframe,我们可以观察.load()事件
  2. 如果已加载iframe,我们需要检查readyState
  3. 如果readyStatecomplete,我们通常可以假设已经加载了iframe。但是,由于Chrome的上述行为,我们还需要检查它是否为空页的readyState
  4. 如果是这样,我们需要观察间隔中的readyState以检查实际文档(与src属性相关)是否为complete
  5. 我用以下功能解决了这个问题。已经(转化为ES5)成功测试了

    • Chrome 49
    • Safari 5
    • Firefox 45
    • IE 8,9,10,11
    • Edge 24
    • iOS 8.0(“Safari Mobile”)
    • Android 4.0(“浏览器”)

    取自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();
        }
    };
    

    工作示例

    由两个文件组成(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的

    <!doctype html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>Child</title>
    </head>
    <body>
        <p>Lorem ipsum</p>
    </body>
    </html>
    

    您还可以将src内的index.html属性更改为“http://example.com/”。只是玩弄它。

答案 1 :(得分:1)

我会使用postMessage。 iframe可以分配自己的onload事件并发布到父级。如果存在计时问题,请确保在创建iframe之前分配父级的postMessage处理程序。

为此,iframe必须知道父级的url,例如将GET参数传递给iframe。

答案 2 :(得分:1)

我遇到了同样的问题,就我而言,我只是检查了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]

答案 3 :(得分:0)

我非常努力地找到一个跨浏览器的解决方案。重要提示:我无法找到这样的解决方案。但就我而言:

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

我正在使用它:

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函数。
  • 请注意,其中一些问题只会在页面上加载大量其他内容时发生,这会使这些事情的时间安排变得不那么确定。

因此,如果有人能够改进这一点,我们将非常感激。

答案 4 :(得分:-1)

只有当iframe中的内容被加载时,innerDoc才为真,并在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);
 });};

example

答案 5 :(得分:-1)

我认为你应该尝试使用onreadystatechange事件。

http://jsfiddle.net/fk8fc/3/

$(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:这是我根据上述概念制作的打包功能:

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