连接到网页 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.
我正在 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.