Greasy Fork is available in English.

ElementGetter_gf

这是一个异步获取元素的脚本库。不少人写脚本都碰到过元素延迟加载的问题,使用定时器获取不仅实时性不足,还有性能问题,DOMNodeInserted的性能也不好,一般都推荐MutationObserver的方案。但是MutationObserver的语法较复杂,回调函数的写法也不易于使用,因此本库将相关代码加以封装,让元素获取一步到位,方便脚本的快速开发。

Bu script direkt olarak kurulamaz. Başka scriptler için bir kütüphanedir ve meta yönergeleri içerir // @require https://update.greasyfork.org/scripts/477884/1267853/ElementGetter_gf.js

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