异步获取元素的脚本库waitForKeyElements
今天看了cxxjackie大佬写的异步获取元素的脚本库 ElementGetter,很有用,老哥写的的ElementGetter
是来解决异步获取元素问题的。我也遇到过这种元素延迟加载的问题,也想封个函数来着,可惜不会写。
这类问题其实很常见,比如对评论区,或者对正在编辑的文字样式修改,还有对网盘文件列表元素的修改。那么在ElementGetter
出来之前,是要如何解决这类问题的呢?总的来说,有两个常见的方法:
- 1)使用setInterval来进行循环判断
- 2)通过DOM插入监控来判断。
两种方法优劣cxxjackie也说了:使用定时器获取不仅实时性不足,还有性能问题,DOMNodeInserted的性能也不好,一般都推荐MutationObserver的方案。但是MutationObserver的语法较复杂,回调函数的写法也不易于使用。
ElementGetter
应该属于后一种,即对DOM插入监控的封装;前一种用定时器延时的方法,十年之前也有BrockA老哥封装,这就是今天要谈的异步获取元素的脚本库waitForKeyElements
waitForKeyElements库
- 能干什么:作为异步获取元素的小工具函数
- 正宗源地址:gist.github.com/BrockA/2625891
- 用例:
waitForKeyElements ("div.comments", commentCallbackFunction);
- 依赖:jquery库
- 参数:前两个必选,后两个可选,总共四个
- waitForKeyElements函数的四个参数罗列
- selectorTxt
- actionFunction
- bWaitOnce
- iframeSelector
这个函数能干什么,用作者BrockA的话说:A utility function, for Greasemonkey scripts, that detects and handles AJAXed content.关于源代码的地址,有必要说明一下,如果直接在Github上搜,第一条结果是CoeJoder对原作者BrockA的一个fork,使用起来有区别,本文谈的还是gist上原作者的那个。
waitForKeyElements函数用到四个参数,这里贴一下作者的注释
function waitForKeyElements (
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
)
- selectorTxt这个必填,就是对元素querySelector的选择器;
- actionFunction这个也必填,就是对选择器选中的元素执行的回调函数,把干活的callback函数传进去,你自己写的回调函数传入的参数是jNode,也就是yourcallback(jnode)
- bWaitOnce可选,没用过;
- iframeSelector这个也可选,是对大的iframe的选择器
actionFunction这个参数要求callback函数里面传jNode,从这里也能看出来,这个函数库依赖Jquery库,比如
function yourcallbackfunc(jNode){
var yourElem = jNode[0];
// var yourElem = jNode.get(0);
}
你自己写一个针对selectorTxt
选择器选出来的元素的回调函数yourcallbackfunc
,里面可以通过jNode[0]
或者jNode.get(0)
来获取原生的DOM,具体How do I pull a native DOM element from a jQuery object?。可以把yourcallbackfunc
作为第二个必选参数actionFunction
传给waitForKeyElements
函数;
bWaitOnce
可选,实际我没用过;
iframeSelector
也可选,默认不填的话前面的选择器是在document选,填的话,选择器selectorTxt
作用于这个iframeSelector
选出来的iframe里。
iframeSelector
这个可选参数还是挺有用的,最初我发现这个库也是在解决类似樱花动漫问题的,要选择iframe内异步加载元素的背景下,原作者BrockA的waitForKeyElements针对这种情况好用,CoeJoder的魔改版我试着用过,不知怎么的却会出现跨域问题,不知道是不是我使用姿势不对。
CoeJoder这位老哥在BrockA评论区也自夸了一番,自称有移除了Jquery依赖等几个好处:
This is a great script! I have modified it with what I feel are some important improvements (not requiring jQuery, not using setInterval()
, allowing a selector function instead of a string).
不过值得注意的是,CoeJoder的fork和原版不兼容,是五个参数,用起来也有区别。作者BrockA也提醒他可以用MutationObserver来实现
That's good, @CoeJoder. You might also consider one of the other forks (almost 100 on Gist alone) that uses MutationObserver.
这里提到的其他思路其实就是类似cxxjackie写的ElementGetter
这样基于MutationObserver的实现。
jquery依赖
扯远了,回到waitForKeyElements函数,它依赖jquery,这其实也算一个小缺点,尤其是在我们cdn很容易抽风的网络环境下。不过张正则同志已经为我们找到了免费好用的超星cdn
// @require https://z.chaoxing.com/js/jquery-3.5.0.min.js
谢谢你,因为有你,我心中只有感恩。
按道理,waitForKeyElements这个库也应该有个正规的cdn,不过Github的gist我们访问不了,一个凑合着能用的方法是依赖greasyfork上其他人发布的脚本。
// @require https://greasyfork.org/scripts/383527-wait-for-key-elements/code/Wait_for_key_elements.js?version=701631
这个脚本也不纯粹,最好还是有老哥搞个正规的cdn或者搬运到脚本猫来。在本文末尾,将贴出整个库,不想用waitForKeyElements的cdn,可以直接把代码复制到自己脚本里,并不优雅,但好多人这么做的,也算没办法的办法。
例子
杂七杂八的例子
- how-to-use-waitforkeyelements-to-display-information-after-select-images作者Brock Adams也来自夸了
- run-greasemonkey-script-on-the-same-page-multiple-times
The simplest, most robust way is to use the waitForKeyElements() utility.
Here is a complete script that uses jQuery and waitForKeyElements to alter Amazon search results
(作者的又一次自夸)这是针对亚马逊网站上搜索结果的用例,这里也谈了jNode的问题,可以一看
jNode is a jQuery object. You would use jNode.html()
- using-waitforkeyelements-how-to-prevent-the-key-element-from-being-displayed-and-only-display-it
I decided to pull in jQuery and a function designed for Greasemonkey scripts (waitForKeyElements) that helps with AJAX requests. These are brought in with the @require tag.
不谈中国网络环境的话,理想情况正规可以这样引入
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
- avoid-production-mistakes
- javascript-fire-greasemonkey-script-on-ajax-request
- 使用 GreaseMonkey 脚本在知乎的【消息】中隐藏所有专栏更新消息 知乎上有人写的关于消息提醒的用例
CB站添加recurbate链接按钮(异步获取元素)
看了上面网络上的一些例子,应该基本掌握waitForKeyElements的用法了,我也搞了一个完整的小例子:CB站添加recurbate链接按钮,无耻地推荐,fbi warning,仅供参考,其实有用的就一句话
waitForKeyElements(".room_list_room",handleEveryRoom);
两个必选参数,前面是选择器,后面是回调函数。
题外话,可能有人不知道CB站是什么,其实它和油猴中文网一样,是个写js脚本的网站,你可以到chaturbate.com/apps上发布脚本,不过貌似不是油猴脚本,只是简单的js脚本。同时它支持在线直播写代码(希望油猴中文网也能与国际接轨),上面这行代码选中了所有直播房间,然后用一个回调函数handleEveryRoom
进行处理,回调函数接受jNode为参数,在回调函数里可以用jNode[0]或者jNode.get(0)来获取每一个room的原生DOM元素。
不用waitForKeyElements
而是直接querySelector可以做,但因为直播房间会在不重载网页的条件下更新元素,只用querySelector添加上的按钮会被刷掉,所以还要用定时;waitForKeyElements
这个函数就帮我们把这一步干完了。
waitForKeyElements源码
最后附上BrockAのwaitForKeyElements源码:
- 依赖jquery库,需要先引入
// @require https://z.chaoxing.com/js/jquery-3.5.0.min.js
-
waitForKeyElements源码
/*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
that detects and handles AJAXed content.
Usage example:
waitForKeyElements (
"div.comments"
, commentCallbackFunction
);
//--- Page-specific function to do what we want when the node is found.
function commentCallbackFunction (jNode) {
jNode.text ("This comment changed by waitForKeyElements().");
}
IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements (
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined")
targetNodes = $(selectorTxt);
else
targetNodes = $(iframeSelector).contents ()
.find (selectorTxt);
if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
/*--- Found target node(s). Go through each and act if they
are new.
*/
targetNodes.each ( function () {
var jThis = $(this);
var alreadyFound = jThis.data ('alreadyFound') || false;
if (!alreadyFound) {
//--- Call the payload function.
var cancelFound = actionFunction (jThis);
if (cancelFound)
btargetsFound = false;
else
jThis.data ('alreadyFound', true);
}
} );
}
else {
btargetsFound = false;
}
//--- Get the timer-control variable for this selector.
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace (/[^\w]/g, "_");
var timeControl = controlObj [controlKey];
//--- Now set or clear the timer as appropriate.
if (btargetsFound && bWaitOnce && timeControl) {
//--- The only condition where we need to clear the timer.
clearInterval (timeControl);
delete controlObj [controlKey]
}
else {
//--- Set a timer, if needed.
if ( ! timeControl) {
timeControl = setInterval ( function () {
waitForKeyElements ( selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj [controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}