异步获取元素的脚本库waitForKeyElements
# 异步获取元素的脚本库waitForKeyElements今天看了cxxjackie大佬写的[异步获取元素的脚本库 ElementGetter](
https://bbs.tampermonkey.net.cn/thread-2726-1-1.html),很有用,老哥写的的[`ElementGetter`](https://scriptcat.org/script-show-page/513/code)是来解决异步获取元素问题的。我也遇到过这种元素延迟加载的问题,也想封个函数来着,可惜不会写。
这类问题其实很常见,比如对评论区,或者对正在编辑的文字样式修改,还有对网盘文件列表元素的修改。那么在[`ElementGetter`](https://scriptcat.org/script-show-page/513/code)出来之前,是要如何解决这类问题的呢?总的来说,有两个常见的方法:
- 1)使用setInterval来进行循环判断
- 2)通过DOM插入监控来判断。
两种方法优劣cxxjackie也说了:使用定时器获取不仅实时性不足,还有性能问题,DOMNodeInserted的性能也不好,一般都推荐MutationObserver的方案。但是MutationObserver的语法较复杂,回调函数的写法也不易于使用。
[`ElementGetter`](https://scriptcat.org/script-show-page/513/code)应该属于后一种,即对DOM插入监控的封装;前一种用定时器延时的方法,十年之前也有BrockA老哥封装,这就是今天要谈的异步获取元素的脚本库[`waitForKeyElements`](https://gist.github.com/BrockA/2625891)
## waitForKeyElements库
- 能干什么:作为异步获取元素的小工具函数
- **正宗**源地址:(https://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上搜,第一条结果是(https://github.com/CoeJoder/waitForKeyElements.js)对原作者(https://gist.github.com/BrockA/2625891)的一个fork,使用起来有区别,本文谈的还是gist上原作者的那个。
waitForKeyElements函数用到四个参数,这里贴一下作者的注释
```javascript
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库,比如
```javascript
function yourcallbackfunc(jNode){
var yourElem = jNode;
// var yourElem = jNode.get(0);
}
```
你自己写一个针对`selectorTxt`选择器选出来的元素的回调函数`yourcallbackfunc`,里面可以通过`jNode`或者`jNode.get(0)`来获取原生的DOM,具体(https://learn.jquery.com/using-jquery-core/faq/how-do-i-pull-a-native-dom-element-from-a-jquery-object/)。可以把`yourcallbackfunc`作为第二个必选参数`actionFunction`传给`waitForKeyElements`函数;
`bWaitOnce`可选,实际我没用过;
`iframeSelector`也可选,默认不填的话前面的选择器是在document选,填的话,选择器`selectorTxt`作用于这个`iframeSelector`选出来的iframe里。
`iframeSelector`这个可选参数还是挺有用的,最初我发现这个库也是在解决类似樱花动漫问题的,要选择iframe内异步加载元素的背景下,原作者(https://gist.github.com/BrockA/2625891)的waitForKeyElements针对这种情况好用,CoeJoder的魔改版我试着用过,不知怎么的却会出现跨域问题,不知道是不是我使用姿势不对。
CoeJoder这位老哥在(https://gist.github.com/BrockA/2625891)评论区也自夸了一番,自称有移除了Jquery依赖等几个好处:
This is a great script! I have (https://github.com/CoeJoder/waitForKeyElements.js) 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`](https://scriptcat.org/script-show-page/513/code)这样基于MutationObserver的实现。
### jquery依赖
扯远了,回到waitForKeyElements函数,它依赖jquery,这其实也算一个小缺点,尤其是在我们cdn很容易抽风的网络环境下。不过张正则同志已经为我们找到了免费好用的超星cdn
```
// @require https://z.chaoxing.com/js/jquery-3.5.0.min.js
```
谢谢你,因为有你,我心中只有感恩。
按道理,waitForKeyElements这个库也应该有个正规的cdn,不过Github的(https://gist.github.com/BrockA/2625891)我们访问不了,一个凑合着能用的方法是依赖greasyfork上其他人发布的脚本。
```
// @require https://greasyfork.org/scripts/383527-wait-for-key-elements/code/Wait_for_key_elements.js?version=701631
```
这个脚本也不纯粹,最好还是有老哥搞个正规的cdn或者搬运到脚本猫来。在本文末尾,将贴出整个库,不想用waitForKeyElements的cdn,可以直接把代码复制到自己脚本里,并不优雅,但好多人这么做的,也算没办法的办法。
## 例子
### 杂七杂八的例子
- (https://stackoverflow.com/questions/19238791/how-to-use-waitforkeyelements-to-display-information-after-select-images)作者Brock Adams也来自夸了
- (https://stackoverflow.com/questions/11195658/run-greasemonkey-script-on-the-same-page-multiple-times/11197969#11197969)
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()
- (https://www.adoclib.com/blog/using-waitforkeyelements-how-to-prevent-the-key-element-from-being-displayed-and-only-display-it.html)
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.
不谈中国网络环境的话,理想情况正规可以这样引入
```javascript
// @requirehttp://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @requirehttps://gist.github.com/raw/2625891/waitForKeyElements.js
```
- (https://spin.atomicobject.com/2018/05/07/avoid-production-mistakes/)
- (https://jonic.cn/qa/?qa=435903/javascript-fire-greasemonkey-script-on-ajax-request)
- [使用 GreaseMonkey 脚本在知乎的【消息】中隐藏所有专栏更新消息](https://www.zhihu.com/column/p/26974013 ) 知乎上有人写的关于消息提醒的用例
### CB站添加recurbate链接按钮(异步获取元素)
看了上面网络上的一些例子,应该基本掌握waitForKeyElements的用法了,我也搞了一个完整的小例子:(https://scriptcat.org/script-show-page/397/code),无耻地推荐,fbi warning,仅供参考,其实有用的就一句话
```javascript
waitForKeyElements(".room_list_room",handleEveryRoom);
```
两个必选参数,前面是选择器,后面是回调函数。
题外话,可能有人不知道CB站是什么,其实它和油猴中文网一样,是个写js脚本的网站,你可以到(https://chaturbate.com/apps/)上发布脚本,不过貌似不是油猴脚本,只是简单的js脚本。同时它支持在线直播写代码(希望油猴中文网也能与国际接轨),上面这行代码选中了所有直播房间,然后用一个回调函数`handleEveryRoom`进行处理,回调函数接受jNode为参数,在回调函数里可以用jNode或者jNode.get(0)来获取每一个room的原生DOM元素。
不用`waitForKeyElements`而是直接querySelector可以做,但因为直播房间会在不重载网页的条件下更新元素,只用querySelector添加上的按钮会被刷掉,所以还要用定时;`waitForKeyElements`这个函数就帮我们把这一步干完了。
## waitForKeyElements源码
最后附上(https://gist.github.com/BrockA/2625891)源码:
- 依赖jquery库,需要先引入
```
// @require https://z.chaoxing.com/js/jquery-3.5.0.min.js
```
- waitForKeyElements源码
```javascript
/*--- 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 ;
//--- 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
}
else {
//--- Set a timer, if needed.
if ( ! timeControl) {
timeControl = setInterval ( function () {
waitForKeyElements ( selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}
``` 这两天就完善一下脚本猫的发库的问题 嗯...
信息密度太高了
感觉哥哥如果拆开聊都能讲三四节 这个封装其实不复杂,他加jquery就是为了支持jquery的选择器,比如:contains,要去依赖的话可以全换成querySelector,这样就只支持原生的选择器。他这个iframe的参数是因为不支持父节点(其实是jquery的问题),像我那个库要解决这类问题,只需将iframe的contentDocument作为父节点传入即可。还有个问题是处理shadowDOM,我可以将shadowRoot作为父节点传入,他这个看来就没办法了,除非改动源码,再增加1个参数,那就不如一个parent解决所有问题了。这就是jquery的双刃剑,在提供更多方便选择器的同时,也在一定程度上造成新的麻烦。
我其实也考虑过扩展一下选择器,包含部分jquery选择器,甚至是shadowDOM的选择器,类似这样的语法:
#id1::shadow .class1
(::shadow并非原创,定际是个被废弃的方案。)iframe也可以加类似的选择器,但要实现这些,至少有以下几个问题:
如果shadowDOM的mode为closed,是否自动对attachShadow进行劫持?要智能到这一步的话,脚本必须运行于document-start。
对于跨域的iframe,是肯定获取不到元素,而同域的iframe,有时会有加载时机的问题,即iframe还没有加载完,contentDocument是空的,必须再去监听一下他的load事件。
这些问题不是不能解决,只是所有东西加起来,代码量会变得很大,增加几百行代码来实现多数人用不到的特性,这样做是否值得?最终我决定把问题交给用户,你自己来处理父节点,如有必要,再封装一个获取shadowRoot和iframe的库就是了,但这不应该成为ElementGetter的目标。
另外扯点题外话,ElementGetter的create方法就有点“多余特性”的意思,好在代码也就几行,而且还挺方便的(至少我很常用),也就顺手加上去了。 ggnb666!!! 确实会有shadowDOM不能搞的问题,看来还是新出的这个更好,等脚本猫发库稳定了(鞭挞更新.jpg),就换用ElementGetter。我是看用这个的人还挺多,开始也没看懂用法,就顺便说一下。 朱焱伟 发表于 2022-7-11 22:46
确实会有shadowDOM不能搞的问题,看来还是新出的这个更好,等脚本猫发库稳定了(鞭挞更新.jpg),就换用Ele ...
我已经换了,{:4_94:} 本帖最后由 Wease2 于 2023-4-20 14:53 编辑
Hire Ukrainian Mobile App Developers https://devlight.io/blog/hire-top-rated-app-developers-in-ukraine/ is one of the best decisions I have ever made. I needed a reliable team to build my app from scratch, and Devlight exceeded my expectations. They were professional, knowledgeable and communicative throughout the entire process. In addition, their rates were extremely competitive, which made them the obvious choice for my project. I highly recommend them to anyone looking for the best app developers in Ukraine.
页:
[1]