GF_ElementGetter

cxxjackie大佬的异步获取元素的脚本库,备份自用,使用方法见https://bbs.tampermonkey.net.cn/thread-2726-1-1.html

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/474385/1535220/GF_ElementGetter.js

  1. // ==UserScript==
  2. // @name GF_ElementGetter
  3. // @author cxxjackie
  4. // @version 2.0.1
  5. // @description cxxjackie大佬的异步获取元素的脚本库,备份自用,使用方法见https://bbs.tampermonkey.net.cn/thread-2726-1-1.html
  6. // @supportURL https://bbs.tampermonkey.net.cn/thread-2726-1-1.html
  7. // @original-script https://scriptcat.org/script-show-page/513
  8. // @original-author cxxjackie
  9. // ==/UserScript==
  10.  
  11. var elmGetter = function() {
  12. const win = window.unsafeWindow || document.defaultView || window;
  13. const doc = win.document;
  14. const listeners = new WeakMap();
  15. let mode = 'css';
  16. let $;
  17. const elProto = win.Element.prototype;
  18. const matches = elProto.matches || elProto.matchesSelector || elProto.webkitMatchesSelector ||
  19. elProto.mozMatchesSelector || elProto.oMatchesSelector;
  20. const MutationObs = win.MutationObserver || win.WebkitMutationObserver || win.MozMutationObserver;
  21. function addObserver(target, callback) {
  22. const observer = new MutationObs(mutations => {
  23. for (const mutation of mutations) {
  24. if (mutation.type === 'attributes') {
  25. callback(mutation.target, 'attr');
  26. if (observer.canceled) return;
  27. }
  28. for (const node of mutation.addedNodes) {
  29. if (node instanceof Element) callback(node, 'insert');
  30. if (observer.canceled) return;
  31. }
  32. }
  33. });
  34. observer.canceled = false;
  35. observer.observe(target, {childList: true, subtree: true, attributes: true, attributeOldValue: true});
  36. return () => {
  37. observer.canceled = true;
  38. observer.disconnect();
  39. };
  40. }
  41. function addFilter(target, filter) {
  42. let listener = listeners.get(target);
  43. if (!listener) {
  44. listener = {
  45. filters: new Set(),
  46. remove: addObserver(target, (el, reason) => listener.filters.forEach(f => f(el, reason)))
  47. };
  48. listeners.set(target, listener);
  49. }
  50. listener.filters.add(filter);
  51. }
  52. function removeFilter(target, filter) {
  53. const listener = listeners.get(target);
  54. if (!listener) return;
  55. listener.filters.delete(filter);
  56. if (!listener.filters.size) {
  57. listener.remove();
  58. listeners.delete(target);
  59. }
  60. }
  61. function query(selector, parent, root, curMode, reason) {
  62. switch (curMode) {
  63. case 'css': {
  64. if (reason === 'attr') return matches.call(parent, selector) ? parent : null;
  65. const checkParent = parent !== root && matches.call(parent, selector);
  66. return checkParent ? parent : parent.querySelector(selector);
  67. }
  68. case 'jquery': {
  69. if (reason === 'attr') return $(parent).is(selector) ? $(parent) : null;
  70. const jNodes = $(parent !== root ? parent : []).add([...parent.querySelectorAll('*')]).filter(selector);
  71. return jNodes.length ? $(jNodes.get(0)) : null;
  72. }
  73. case 'xpath': {
  74. const ownerDoc = parent.ownerDocument || parent;
  75. selector += '/self::*';
  76. return ownerDoc.evaluate(selector, reason === 'attr' ? root : parent, null, 9, null).singleNodeValue;
  77. }
  78. }
  79. }
  80. function queryAll(selector, parent, root, curMode, reason) {
  81. switch (curMode) {
  82. case 'css': {
  83. if (reason === 'attr') return matches.call(parent, selector) ? [parent] : [];
  84. const checkParent = parent !== root && matches.call(parent, selector);
  85. const result = parent.querySelectorAll(selector);
  86. return checkParent ? [parent, ...result] : [...result];
  87. }
  88. case 'jquery': {
  89. if (reason === 'attr') return $(parent).is(selector) ? [$(parent)] : [];
  90. const jNodes = $(parent !== root ? parent : []).add([...parent.querySelectorAll('*')]).filter(selector);
  91. return $.map(jNodes, el => $(el));
  92. }
  93. case 'xpath': {
  94. const ownerDoc = parent.ownerDocument || parent;
  95. selector += '/self::*';
  96. const xPathResult = ownerDoc.evaluate(selector, reason === 'attr' ? root : parent, null, 7, null);
  97. const result = [];
  98. for (let i = 0; i < xPathResult.snapshotLength; i++) {
  99. result.push(xPathResult.snapshotItem(i));
  100. }
  101. return result;
  102. }
  103. }
  104. }
  105. function isJquery(jq) {
  106. return jq && jq.fn && typeof jq.fn.jquery === 'string';
  107. }
  108. function getOne(selector, parent, timeout) {
  109. const curMode = mode;
  110. return new Promise(resolve => {
  111. const node = query(selector, parent, parent, curMode);
  112. if (node) return resolve(node);
  113. let timer;
  114. const filter = (el, reason) => {
  115. const node = query(selector, el, parent, curMode, reason);
  116. if (node) {
  117. removeFilter(parent, filter);
  118. timer && clearTimeout(timer);
  119. resolve(node);
  120. }
  121. };
  122. addFilter(parent, filter);
  123. if (timeout > 0) {
  124. timer = setTimeout(() => {
  125. removeFilter(parent, filter);
  126. resolve(null);
  127. }, timeout);
  128. }
  129. });
  130. }
  131. return {
  132. get currentSelector() {
  133. return mode;
  134. },
  135. get(selector, ...args) {
  136. let parent = typeof args[0] !== 'number' && args.shift() || doc;
  137. if (mode === 'jquery' && parent instanceof $) parent = parent.get(0);
  138. const timeout = args[0] || 0;
  139. if (Array.isArray(selector)) {
  140. return Promise.all(selector.map(s => getOne(s, parent, timeout)));
  141. }
  142. return getOne(selector, parent, timeout);
  143. },
  144. each(selector, ...args) {
  145. let parent = typeof args[0] !== 'function' && args.shift() || doc;
  146. if (mode === 'jquery' && parent instanceof $) parent = parent.get(0);
  147. const callback = args[0];
  148. const curMode = mode;
  149. const refs = new WeakSet();
  150. for (const node of queryAll(selector, parent, parent, curMode)) {
  151. refs.add(curMode === 'jquery' ? node.get(0) : node);
  152. if (callback(node, false) === false) return;
  153. }
  154. const filter = (el, reason) => {
  155. for (const node of queryAll(selector, el, parent, curMode, reason)) {
  156. const _el = curMode === 'jquery' ? node.get(0) : node;
  157. if (refs.has(_el)) break;
  158. refs.add(_el);
  159. if (callback(node, true) === false) {
  160. return removeFilter(parent, filter);
  161. }
  162. }
  163. };
  164. addFilter(parent, filter);
  165. },
  166. create(domString, ...args) {
  167. const returnList = typeof args[0] === 'boolean' && args.shift();
  168. const parent = args[0];
  169. const template = doc.createElement('template');
  170. template.innerHTML = domString;
  171. const node = template.content.firstElementChild;
  172. if (!node) return null;
  173. parent ? parent.appendChild(node) : node.remove();
  174. if (returnList) {
  175. const list = {};
  176. node.querySelectorAll('[id]').forEach(el => list[el.id] = el);
  177. list[0] = node;
  178. return list;
  179. }
  180. return node;
  181. },
  182. selector(desc) {
  183. switch (true) {
  184. case isJquery(desc):
  185. $ = desc;
  186. return mode = 'jquery';
  187. case !desc || typeof desc.toLowerCase !== 'function':
  188. return mode = 'css';
  189. case desc.toLowerCase() === 'jquery':
  190. for (const jq of [window.jQuery, window.$, win.jQuery, win.$]) {
  191. if (isJquery(jq)) {
  192. $ = jq;
  193. break;
  194. }
  195. }
  196. return mode = $ ? 'jquery' : 'css';
  197. case desc.toLowerCase() === 'xpath':
  198. return mode = 'xpath';
  199. default:
  200. return mode = 'css';
  201. }
  202. }
  203. };
  204. }();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址