37

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

我真正想要的是拥有一个在 iframe 加载时或加载后总是调用一次的函数。为了更清楚地说明这一点,这里有两种情况:

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

我怎样才能做到这一点?

4

8 回答 8

51

我一直用头撞墙,直到我发现这里发生了什么。

背景资料

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

解决方案

以下是必要的:

  1. 如果 iframe 尚未加载,我们可以观察.load()事件
  2. 如果 iframe 已经加载,我们需要检查readyState
  3. 如果readyStatecomplete,我们通常可以假设 iframe 已经加载。但是,由于 Chrome 的上述行为,我们还需要检查它是否readyState是空页面
  4. 如果是这样,我们需要readyState在一个时间间隔内观察,以检查实际文档(与 src 属性相关)是否为complete

我已经用以下函数解决了这个问题。它已经(转译为 ES5)成功地在

  • 铬 49
  • 野生动物园 5
  • 火狐 45
  • 即 8、9、10、11
  • 边缘 24
  • iOS 8.0(“Safari 移动版”)
  • 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/ ”。只是玩弄它。

于 2016-03-22T13:23:13.640 回答
3

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

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

于 2013-06-18T07:29:52.250 回答
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]
于 2019-01-28T09:46:11.607 回答
1

如果 iFrame 已加载,此函数将立即运行您的回调函数,或者等到 iFrame 完全加载后再运行您的回调函数。只需将您想要在 iFrame 完成加载时运行的回调函数和元素传递给此函数:

function iframeReady(callback, iframeElement) {
    const iframeWindow = iframeElement.contentWindow;
    if ((iframeElement.src == "about:blank" || (iframeElement.src != "about:blank" && iframeWindow.location.href != "about:blank")) && iframeWindow.document.readyState == "complete") {
        callback();
    } else {
        iframeWindow.addEventListener("load", callback);
    }
}

这将解决最常见的问题,例如 chrome 使用 about:blank 初始化 iframe 和 iFrame 不支持 DOMContentLoaded 事件。请参阅此https://stackoverflow.com/a/69694808/15757382答案以获得解释。

于 2021-10-24T07:58:24.880 回答
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 函数几乎是从外部清除的。
  • 请注意,其中一些问题仅在页面上加载了许多其他内容时才会发生,这使得这些内容的时间不确定性降低。

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

于 2013-06-27T18:54:20.167 回答
0

这是我在没有 jQuery 依赖的 Typescript 中的解决方案

export function whenIframeLoaded(iframe: HTMLIFrameElement): Promise<void> {
    return new Promise((resolve, reject) => {
        try {
            const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
            if (iframeDocument.readyState === 'complete') {
                resolve();
            } else {
                iframe.addEventListener(
                    'load', () => resolve(),
                    { once: true }
                );
            }
        } catch {
            reject();
        }
    });
}

我怎么称呼它

const iframe = document.getElementById(iframeName) as HTMLIFrameElement;
await whenIframeLoaded(iframe);
于 2022-02-02T15:19:45.053 回答
-1

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

例子

于 2013-06-18T07:23:52.420 回答
-1

我认为你应该尝试使用onreadystatechangeevent.

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);
    }
}
于 2013-06-19T23:08:11.040 回答