上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

异步获取元素的脚本库waitForKeyElements

[复制链接]
  • TA的每日心情

    前天 09:23
  • 签到天数: 112 天

    [LV.6]常住居民II

    4

    主题

    18

    帖子

    171

    积分

    荣誉开发者

    Rank: 10Rank: 10Rank: 10

    积分
    171

    荣誉开发者国庆纪念章喜迎中秋

    发表于 2022-7-11 00:51:54 | 显示全部楼层 | 阅读模式

    异步获取元素的脚本库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;
      }
    已有1人评分好评 油猫币 贡献 理由
    王一之 + 1 + 4 + 1 感谢分享

    查看全部评分 总评分:好评 +1  油猫币 +4  贡献 +1 

    当冥想的日子飞逝,喧嚣的日子把我们唤去,且在此地留下些微的痕迹
  • TA的每日心情
    开心
    昨天 13:55
  • 签到天数: 100 天

    [LV.6]常住居民II

    171

    主题

    2253

    帖子

    2299

    积分

    管理员

    Rank: 10Rank: 10Rank: 10

    积分
    2299

    荣誉开发者喜迎中秋热心会员活跃会员突出贡献三好学生管理员家财万贯

    发表于 2022-7-11 09:52:21 | 显示全部楼层
    这两天就完善一下脚本猫的发库的问题
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。/ 微信公众号:一之哥哥
    回复

    使用道具 举报

  • TA的每日心情
    开心
    前天 23:59
  • 签到天数: 88 天

    [LV.6]常住居民II

    386

    主题

    3405

    帖子

    3388

    积分

    管理员

    非物质文化遗产社会摇传承人

    Rank: 10Rank: 10Rank: 10

    积分
    3388

    喜迎中秋国庆纪念章荣誉开发者家财万贯管理员

    发表于 2022-7-11 10:20:22 | 显示全部楼层
    嗯...
    信息密度太高了
    感觉哥哥如果拆开聊都能讲三四节
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    15

    主题

    479

    帖子

    836

    积分

    荣誉开发者

    Rank: 10Rank: 10Rank: 10

    积分
    836

    卓越贡献活跃会员热心会员突出贡献三好学生荣誉开发者喜迎中秋

    发表于 2022-7-11 12:08:18 | 显示全部楼层
    这个封装其实不复杂,他加jquery就是为了支持jquery的选择器,比如:contains,要去依赖的话可以全换成querySelector,这样就只支持原生的选择器。他这个iframe的参数是因为不支持父节点(其实是jquery的问题),像我那个库要解决这类问题,只需将iframe的contentDocument作为父节点传入即可。还有个问题是处理shadowDOM,我可以将shadowRoot作为父节点传入,他这个看来就没办法了,除非改动源码,再增加1个参数,那就不如一个parent解决所有问题了。这就是jquery的双刃剑,在提供更多方便选择器的同时,也在一定程度上造成新的麻烦。
    我其实也考虑过扩展一下选择器,包含部分jquery选择器,甚至是shadowDOM的选择器,类似这样的语法:
    1. #id1::shadow .class1
    复制代码

    (::shadow并非原创,定际是个被废弃的方案。)iframe也可以加类似的选择器,但要实现这些,至少有以下几个问题:
    如果shadowDOM的mode为closed,是否自动对attachShadow进行劫持?要智能到这一步的话,脚本必须运行于document-start。
    对于跨域的iframe,是肯定获取不到元素,而同域的iframe,有时会有加载时机的问题,即iframe还没有加载完,contentDocument是空的,必须再去监听一下他的load事件。
    这些问题不是不能解决,只是所有东西加起来,代码量会变得很大,增加几百行代码来实现多数人用不到的特性,这样做是否值得?最终我决定把问题交给用户,你自己来处理父节点,如有必要,再封装一个获取shadowRoot和iframe的库就是了,但这不应该成为ElementGetter的目标。
    另外扯点题外话,ElementGetter的create方法就有点“多余特性”的意思,好在代码也就几行,而且还挺方便的(至少我很常用),也就顺手加上去了。
    回复

    使用道具 举报

    该用户从未签到

    36

    主题

    233

    帖子

    240

    积分

    荣誉开发者

    Rank: 10Rank: 10Rank: 10

    积分
    240

    荣誉开发者国庆纪念章

    发表于 2022-7-11 18:32:07 | 显示全部楼层
    ggnb666!!!
    回复

    使用道具 举报

  • TA的每日心情

    前天 09:23
  • 签到天数: 112 天

    [LV.6]常住居民II

    4

    主题

    18

    帖子

    171

    积分

    荣誉开发者

    Rank: 10Rank: 10Rank: 10

    积分
    171

    荣誉开发者国庆纪念章喜迎中秋

    发表于 2022-7-11 22:46:00 | 显示全部楼层
    确实会有shadowDOM不能搞的问题,看来还是新出的这个更好,等脚本猫发库稳定了(鞭挞更新.jpg),就换用ElementGetter。我是看用这个的人还挺多,开始也没看懂用法,就顺便说一下。
    当冥想的日子飞逝,喧嚣的日子把我们唤去,且在此地留下些微的痕迹
    回复

    使用道具 举报

    该用户从未签到

    2

    主题

    28

    帖子

    22

    积分

    助理工程师

    Rank: 1

    积分
    22
    发表于 2022-9-14 10:07:45 | 显示全部楼层
    朱焱伟 发表于 2022-7-11 22:46
    确实会有shadowDOM不能搞的问题,看来还是新出的这个更好,等脚本猫发库稳定了(鞭挞更新.jpg),就换用Ele ...

    我已经换了,
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表