Userscript等待页面加载后再执行代码的技巧?

131

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

问题在于,这个特定的页面有时需要一点时间才能加载完成。

我尝试了使用$(function() { });$(window).load(function(){ });包装器,但是似乎都没有起作用,虽然可能是我使用不正确。

我能做的最好的事情就是使用setTimeout(function() { }, 600);,虽然它并不总是可靠。

Greasemonkey中最好的技术是什么,以确保特定的代码将在页面加载完成后执行?


5
您可以使用(function init(){var counter = document.getElementById('id-of-element');if (counter) { /* 对计数器元素进行操作 */ } else { setTimeout(init, 0);}})();来持续轮询元素的存在性。这是最通用的解决方案。 - Rob W
3
油猴的近亲 Tampermonkey 和 Scriptish 支持更多的 @run-at 值,其中包括 document-idlecontext-menu,这可能会有用。另外,看起来油猴正在添加支持 document-idle 尽管它目前还没有被记录下来。 - Mr. Llama
7个回答

110

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

window.addEventListener('load', function() {
    // your code here
}, false);
在您的用户脚本内部。

19
将jQuery添加到脚本中和添加@require http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js一样简单。我已经将它们放到了模板脚本中,因为我通常使用它们。同时加入this.$ = this.jQuery = jQuery.noConflict(true);以避免常见的冲突。 - m3nda
添加依赖项到 jQuery 当 - sleblanc
30
不确定为什么人们如此渴望将jQuery用于所有事情。 - Wyatt Ward
4
@mondjunge,你的经验是错误的,这是标准功能,在用户脚本中也可以使用。 - Alex G.P.
10
在过去的几年中,我只使用了Tampermonkey……它的userscripts完美支持document.querySelector。 - devnull69
显示剩余3条评论

80

这是一个常见的问题,正如你所说,等待页面加载是不够的 - 因为 AJAX 可以在之后长时间改变事物。

针对这些情况,有一个标准(或相似的)稳健实用程序。它就是 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.

    If in Tampermonkey, use "// @unwrap" to enable sandbox instead.
*/

waitForKeyElements ("YOUR_jQUERY_SELECTOR", actionFunction);

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

提供目标页面的确切细节,以获得更具体的示例。


如果目标页面已经有JQuery,该怎么办?在页面上加载JQuery会破坏原始代码,但是我的GM脚本在JQuery运行之前运行。我的场景特定页面是backpack.tf,它正在运行JQuery 1.7.2。 - Jacklynn
4
@Jack,使用jQuery在脚本中不会将其加载到页面中,当您使用@grant GM_addStyle时也不会破坏任何内容。这就是它存在的原因。 - Brock Adams
2
如果您只想让您的代码运行一次,请确保将waitForKeyElements的第三个参数传递为true - Starwarswii

60
我想提供另一种更现代、更优雅的解决方案来解决AJAX问题。
像大多数解决方案一样,Brock的脚本使用setInterval()setTimeout()来定期检查元素,因此无法立即响应,总会有一些延迟。其他解决方案使用onload事件,在动态页面上往往会提前触发。 解决方案:使用MutationObserver()直接监听DOM变化,以便在元素插入后立即响应。
(new MutationObserver(check)).observe(document, {childList: true, subtree: true});

function check(changes, observer) {
    if(document.querySelector('#mySelector')) {
        observer.disconnect();
        // actions to perform after #mySelector is found
    }
}

每次DOM更改后,check函数会立即触发。这允许您指定任意的触发条件,以便在执行自己的代码之前等待页面处于所需的确切状态。
请注意,如果DOM经常更改或者您的条件需要很长时间来评估,这可能会很慢,因此尽量通过观察尽可能小的DOM子树来限制范围,而不是观察document
这种方法非常通用,可以应用于许多情况。要多次响应,只需在触发时不断开观察者的连接。
另一个用例是,如果您不寻找任何特定的元素,而只是等待页面停止更改,您可以将其与一个空闲计时器结合使用,该计时器在页面更改时被重置。
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
}

你可以监听属性和文本的变化。只需在选项中将attributescharacterData设置为true
observer.observe(document, {childList: true, attributes: true, characterData: true, subtree: true});

59

从Greasemonkey 3.6版本(2015年11月20日)开始,元数据键@run-at支持新值document-idle。 将其简单地放置在您的Greasemonkey脚本的元数据块中:

// @run-at      document-idle

根据文档描述:

这个脚本会在页面和所有资源(图片、样式表等)加载完成后以及页面脚本运行之后运行。


12
警告。Greasemonkey 实现这个功能的方式似乎与 Chrome/Tampermonkey 不同。因此,在不同浏览器中脚本的工作方式可能不同。使用类似于 waitForKeyElementsMutationObserver 的方法仍然更加健壮。 - Brock Adams
这个不起作用-1 - undefined

16
将我的脚本封装在 $(window).load(function(){ }) 中,对我从未失败过。
也许您的页面已经完成了加载,但仍有一些 ajax 内容正在加载中。
如果是这种情况,这个来自 Brock Adams 的好代码可以帮助您:
https://gist.github.com/raw/2625891/waitForKeyElements.js 我通常使用它来监视出现在 postback 上的元素。
像这样使用它:waitForKeyElements("elementtowaitfor", functiontocall)

我应该将它粘贴到我的用户脚本文件中吗?我这样做了,但它显示“$未定义”的错误。 - Salem

9
如果您想操作节点,比如获取节点的值或者改变节点的样式,您可以使用这个函数来等待这些节点。
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
})()

这里有个例子 icourse163_enhance

这是一个关于it技术的示例。

3

要检测XHR是否在网页中加载完成,然后触发一些函数。我从如何使用JavaScript在Chrome控制台中存储“XHR已完成加载”消息?中了解到这一点,它确实有效。

    //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使用示例。 在同一页上运行多次Greasemonkey脚本?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接