连接到网页 window.onload 的更简洁的方法?

Cleaner way to hook into the window.onload of a web page?

我正在 Swift 中构建一个小型 iOS 应用程序,我使用 WKWebView. What I am trying to achieve is to be notified when a web page has rendered completely. It is a known issue with WKWebView 它的 none 加载通知有效。

然而,这种方法似乎可行,想法是挂接到 Javascript 中的 window.onload 函数并从那里通知 Swift。但是,我在这里也发现了一个问题。如果我使用 JQuery,在某些页面中显然不会发生回调,因为网页已经定义了 window.onload。如果我使用纯 Javascript 方式,一些网页会中断,即它们根本不会加载,因为我正在覆盖 window.onload.

// using JQuery, this function never get called for web pages that do window.onload = 
$(window).load(function() { 
    window.webkit.messageHandlers.callbackHandler.postMessage(
        JSON.stringify({body: "window finished loading"}));
});
// works always but breaks web pages that use window.onload
window.onload = function() {
    window.webkit.messageHandlers.callbackHandler.postMessage(
        JSON.stringify({body: "window finished loading"}));
};

问题是如何将此行通知附加到现有 window.onload 并定义一个 window.onload(如果它不存在)?也欢迎其他想法,例如排队 window.load 实施?

为了完整起见,我在下面包含了两种已知的使用 WKWebView 本机获取此类通知的方法。

(1) 得到WKNavigationDelegate生命周期通知但是当网页还没有完全呈现时回调通知触发得太早。

class ViewController: UIViewController {
    // ...
    func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
        NSLog("didFinishNavigation callback received ... too early!")
    }
}

(2) 使用Key-Value Observer方法(KVO) 但是这里回调通知触发的太早了:

webView.addObserver(viewController, forKeyPath: "estimatedProgress", options: .New, context: nil)
//
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<()>) {
    guard let webView = object as? WKWebView else {return}
    guard let change = change else {return}
    guard let keyPath = keyPath else {return}
    switch keyPath {
        case "estimatedProgress":
            if ((1.0 - webView.estimatedProgress) < 1e-10) {
                NSLog("'estimatedProgress' callback received ... too early!")
            }
            break
        default: break
    }
}

"loading" KVO 也是同样的问题。

更新: 除了已接受的答案外,这个问题的排名靠前的答案 running-jquery-after-all-other-js-has-executed 也通过轮询 DOM 来解决此 OP一个特定的变化,例如当除了加载页面之外的所有 Javascript 都已完成时。

我找到了一种可行的方法。然而,尽管我试图避免重复通知是徒劳的,但我仍然收到一些网页的两个通知并且还没有找到修复它的方法......

if (window.addEventListener) {
    window.addEventListener("load",
        function() {
            // try to make sure it is called only once ...
            if (typeof window.isMyiOSAppAlreadyNotified === 'undefined') {
                // notify my iOS App that the page has finished loading 
                window.webkit.messageHandlers.callbackHandler.postMessage(
                    JSON.stringify({body: "window onload"}));
            }
            window.isMyiOSAppAlreadyNotified = true;
        }
    );
}

而不是 load event you can listen to DOMContentLoaded

您也可以通过执行以下操作在执行堆栈的末尾执行回调:

window.setTimeout(callback, 0);

此外,您可以尝试在回调中调用 removeEventListener

例如:

if (window.addEventListener) {
    var documentIsReady = function() {
        window.removeEventListener("load", documentIsReady);

        if (typeof window.isMyiOSAppAlreadyNotified === 'undefined') {
            window.webkit.messageHandlers.callbackHandler.postMessage(JSON.stringify({body: "window onload"}));
        }
        window.isMyiOSAppAlreadyNotified = true;
    };
    window.addEventListener("load", function() { window.setTimeout(documentIsReady, 0); });
}

为了避免像 isMyiOSAppAlreadyNotified 这样的变量污染全局 (window) 范围,您可以应用 module pattern.