在执行代码技术之前等待页面加载的用户脚本?

我正在编写一个 Greasemonkey 用户脚本,希望在页面完全完成加载时执行特定的代码,因为它返回一个我想要显示的 div 计数。

问题是,这个特定的页面有时在加载所有内容之前需要一点时间。

我试过了,记录 $(function() { });$(window).load(function(){ });的包装。然而,似乎没有一个适合我,虽然我可能应用它们是错误的。

我能做的最好的是使用一个 setTimeout(function() { }, 600);的工作,虽然它并不总是可靠的。

What is the best technique to use in Greasemonkey to ensure that the specific code will execute when the page finishes loading?

117505 次浏览

Greasemonkey (通常)没有 jQuery,因此常用的方法是使用

window.addEventListener('load', function() {
// your code here
}, false);

在你的用户脚本里

$(window).load(function(){ })包装我的脚本对我来说从未失败过。

也许您的页面已经完成,但仍然有一些 Ajax 内容正在加载。

如果是这样的话,这段来自 布洛克 · 亚当斯的代码可以帮助你:
Https://gist.github.com/raw/2625891/waitforkeyelements.js

我通常使用它来监视出现在 回复上的元素。

像这样使用它: waitForKeyElements("elementtowaitfor", functiontocall)

This is a common problem and, as you've said, waiting for the page load is not enough -- since AJAX can and does change things long after that.

针对这些情况,有一个标准(类似)健壮的实用程序,它是 waitForKeyElements()实用程序

像这样使用它:

// ==UserScript==
// @name     _Wait for delayed or AJAX page load
// @include  http://YOUR_SERVER.COM/YOUR_PATH/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant    GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a major design
change introduced in GM 1.0.
It restores the sandbox.
*/


waitForKeyElements ("YOUR_jQUERY_SELECTOR", actionFunction);


function actionFunction (jNode) {
//-- DO WHAT YOU WANT TO THE TARGETED ELEMENTS HERE.
jNode.css ("background", "yellow"); // example
}

为更具体的示例提供目标页的确切细节。

在 Greasemonkey 3.6(2015年11月20日)时,元数据键 @run-at支持新值 document-idle。 简单地把它放在 Greasemonkey 脚本的元数据块中:

// @run-at      document-idle

文件的描述如下:

该脚本将在页面和所有资源(图像、样式表等)加载后运行,并且页面脚本已经运行。

Brock 的回答 很好,但是我想提供另一种更现代、更优雅的解决 AJAX 问题的方案。

由于他的脚本,像大多数其他人一样,也使用 setInterval()定期检查(300ms) ,它不能立即响应,总是有一个延迟。其他解决方案使用 onload 事件,这些事件通常会在动态页面上提前触发。

您可以使用 MutationObserver()侦听 DOM 更改,并在创建元素后立即响应这些更改

(new MutationObserver(check)).observe(document, {childList: true, subtree: true});


function check(changes, observer) {
if(document.querySelector('#mySelector')) {
observer.disconnect();
// code
}
}

虽然由于 check()会触发每一个 DOM 变化,但是如果 DOM 变化非常频繁,或者您的条件需要很长时间才能得到评估,那么这可能会很慢,所以不要观察 document,尝试通过观察尽可能小的 DOM 子树来限制范围。

This method is very general and can be applied to many situations. To respond multiple times, just don't disconnect the observer when triggered.

另一个用例是,如果你不寻找任何特定的元素,但只是等待页面停止改变,你可以结合倒计时,重置每次页面上的东西改变。

var observer = new MutationObserver(resetTimer);
var timer = setTimeout(action, 3000, observer); // wait for the page to stay still for 3 seconds
observer.observe(document, {childList: true, subtree: true});


// reset timer every time something changes
function resetTimer(changes, observer) {
clearTimeout(timer);
timer = setTimeout(action, 3000, observer);
}


function action(observer) {
observer.disconnect();
// code
}

This method is so versatile, you can listen for attribute and text changes as well. Just set attributes and characterData to true in the options

observer.observe(document, {childList: true, attributes: true, characterData: true, subtree: true});

If you want to manipulate nodes like getting value of nodes or changing style, you can wait for these nodes using this function

const waitFor = (...selectors) => new Promise(resolve => {
const delay = 500
const f = () => {
const elements = selectors.map(selector => document.querySelector(selector))
if (elements.every(element => element != null)) {
resolve(elements)
} else {
setTimeout(f, delay)
}
}
f()
})

然后使用 promise.then

// scripts don't manipulate nodes
waitFor('video', 'div.sbg', 'div.bbg').then(([video, loading, videoPanel])=>{
console.log(video, loading, videoPanel)
// scripts may manipulate these nodes
})

或使用 async&await

//this semicolon is needed if none at end of previous line
;(async () => {
// scripts don't manipulate nodes
const [video, loading, videoPanel] = await waitFor('video','div.sbg','div.bbg')
console.log(video, loading, video)
// scripts may manipulate these nodes
})()

这里是一个例子 增强

检测 XHR 是否完成了网页的加载,然后触发一些功能。 I get this from 如何使用 JavaScript 在 Chrome 的控制台中存储“ XHR 加载完成”消息? and it real works.

    //This overwrites every XHR object's open method with a new function that adds load and error listeners to the XHR request. When the request completes or errors out, the functions have access to the method and url variables that were used with the open method.
//You can do something more useful with method and url than simply passing them into console.log if you wish.
//https://stackoverflow.com/questions/43282885/how-do-i-use-javascript-to-store-xhr-finished-loading-messages-in-the-console
(function() {
var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
this.addEventListener('load', function() {
console.log('XHR finished loading', method, url);
display();
});


this.addEventListener('error', function() {
console.log('XHR errored out', method, url);
});
origOpen.apply(this, arguments);
};
})();
function display(){
//codes to do something;
}

但是如果页面中有许多 XHR,我就不知道如何过滤确定的一个 XHR。

另一个方法是 waitForKeyElements () ,它很不错。 Https://gist.github.com/brocka/2625891
这是给油猴用的样品。 在同一页面上多次运行 Greasemonkey 脚本?