关于javascript:如何检测何时已加载iframe

How to detect when an iframe has already been loaded

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

我真正想要的是拥有一个在加载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属性)。

以下是必要的:

  • 如果尚未加载iframe,我们可以观察.load()事件
  • 如果iframe已经加载,则需要检查readyState
  • 如果readyStatecomplete,我们通常可以假定iframe已经加载。但是,由于Chrome的上述行为,我们还需要检查它是否为空白页面的readyState
  • 如果是这样,我们需要间隔观察readyState来检查实际文档(与src属性相关)是否为complete
  • 我已经通过以下功能解决了这个问题。它已(已转换为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>

    您还可以将index.html中的src属性更改为例如" http://example.com/"。随便玩吧。


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

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


    我有同样的问题。就我而言,我只是检查了onload函数是否被触发。

    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函数在外部已清除。
    • 请注意,其中一些问题仅在页面上加载了许多其他内容时才会发生,这使得这些事情的时间确定性较低。

    因此,如果有人可以对此进行改进,将不胜感激。


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

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