Side Scroll Wheel Pagination

鼠标侧滚轮翻页,支持键盘热键翻页

  1. // ==UserScript==
  2. // @name Side Scroll Wheel Pagination
  3. // @namespace Hal74
  4. // @version 0.1
  5. // @description 鼠标侧滚轮翻页,支持键盘热键翻页
  6. // @author Hal74
  7. // @match *://*/*
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. // 热键翻页开关
  12. const needHotKey = false;
  13. // 编辑下面的数组来自定义规则
  14. const specialXpaths = [
  15. {
  16. //匹配的url
  17. urls: ["taobao.com"],
  18. //上一页节点的xpath
  19. prev: '//button[contains(@aria-label,"上一页")]',
  20. //下一页节点的xpath
  21. next: '//button[contains(@aria-label,"下一页")]',
  22. },
  23. // elementUI
  24. {
  25. urls: ["localhost"],
  26. prev: '//button[@class="btn-prev"]',
  27. next: '//button[@class="btn-next"]',
  28. },
  29. ];
  30.  
  31. const generalXpaths = [
  32. ["//a[(text()='", "')]"],
  33. ["//a[@class='", "']"],
  34. ["//button[(text()='", "')]"],
  35. ["//button[@class='", "']"],
  36. ["//input[@type='button' and @value='", "']"],
  37. ];
  38.  
  39. const Strs = {
  40. next: [
  41. "下一页",
  42. "下页",
  43. "下一页 »",
  44. "下一页 >",
  45. "下一节",
  46. "下一章",
  47. "下一篇",
  48. "后一章",
  49. "后一篇",
  50. "后页>",
  51. "»",
  52. "next",
  53. "next page",
  54. "old",
  55. "older",
  56. "earlier",
  57. "下頁",
  58. "下一頁",
  59. "后一页",
  60. "后一頁",
  61. "翻下页",
  62. "翻下頁",
  63. "后页",
  64. "后頁",
  65. "下翻",
  66. "下一个",
  67. "下一张",
  68. "下一幅",
  69. ],
  70. prev: [
  71. "上一页",
  72. "上页",
  73. "« 上一页",
  74. "< 上一页",
  75. "上一节",
  76. "上一章",
  77. "上一篇",
  78. "前一章",
  79. "前一篇",
  80. "<前页",
  81. "«",
  82. "previous",
  83. "prev",
  84. "previous page",
  85. "new",
  86. "newer",
  87. "later",
  88. "上頁",
  89. "上一頁",
  90. "前一页",
  91. "前一頁",
  92. "翻上页",
  93. "翻上頁",
  94. "前页",
  95. "前頁",
  96. "上翻",
  97. "上一个",
  98. "上一张",
  99. "上一幅",
  100. ]
  101. }
  102.  
  103. const keys = {
  104. prev: [
  105. 'ArrowLeft',
  106. 'a',
  107. ],
  108. next: [
  109. 'ArrowRight',
  110. 'd'
  111. ]
  112. }
  113.  
  114. function checkTextArea(node) {
  115. var name = node.localName.toLowerCase();
  116. if (name === "textarea" || name === "input" || name === "select") {
  117. return true;
  118. }
  119. if (name === "div" && (node.id.toLowerCase().includes("textarea") || node.contentEditable !== 'inherit')) {
  120. return true;
  121. }
  122. return false;
  123. }
  124.  
  125. function hasHorizontalScrollbar(node) {
  126. while (node) {
  127. if ((node.scrollWidth - node.clientWidth) > 20) {
  128. return true;
  129. }
  130. node = node.parentNode;
  131. }
  132. return false;
  133. }
  134.  
  135.  
  136. function xpath(query) {
  137. return unsafeWindow.document.evaluate(
  138. query,
  139. document,
  140. null,
  141. XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
  142. null
  143. );
  144. }
  145.  
  146. function getNode(lnstr) {
  147. var node = getNodeByGeneralXpath(lnstr);
  148. if (!node) node = getNodeBySpecialXpath(lnstr);
  149. return node;
  150. }
  151.  
  152. function getNodeByGeneralXpath(lnstr) {
  153. var strs;
  154. strs = Strs[lnstr];
  155. var x = generalXpaths;
  156. for (var i in x) {
  157. for (var j in strs) {
  158. var query = x[i][0] + strs[j] + x[i][1];
  159. var nodes = xpath(query);
  160. if (nodes.snapshotLength > 0) return nodes.snapshotItem(0);
  161. }
  162. }
  163. return null;
  164. }
  165.  
  166. function getNodeBySpecialXpath(lnstr) {
  167. var s = specialXpaths;
  168. for (var i in s) {
  169. if (checkXpathUrl(s[i].urls)) {
  170. return xpath(s[i][lnstr]).snapshotItem(0);
  171. }
  172. }
  173. return null;
  174. }
  175.  
  176. function checkXpathUrl(urls) {
  177. for (var i in urls) if (location.href.indexOf(urls[i]) >= 0) return true;
  178. return false;
  179. }
  180.  
  181. function throttle(func, delay) {
  182. let lastCall = 0;
  183. return function(...args) {
  184. const now = new Date().getTime();
  185. if (now - lastCall < delay) {
  186. return;
  187. }
  188. lastCall = now;
  189. return func(...args);
  190. };
  191. }
  192.  
  193. function findParentKey(obj, value) {
  194. for (let key in obj) {
  195. if (obj[key].includes(value)) {
  196. return key;
  197. }
  198. }
  199. return null;
  200. }
  201.  
  202. function checkKey(e) {
  203. if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
  204. if (checkTextArea(e.target)) return;
  205. const key = findParentKey(keys, e.key)
  206. const link = getNode(key);
  207. if (key && !!link) {
  208. link.click()
  209. }
  210. }
  211.  
  212. function checkWheel(e) {
  213. if (hasHorizontalScrollbar(e.target)) return
  214. if (event.deltaX !== 0) {
  215. turnPage(event.deltaX);
  216. }
  217. }
  218.  
  219. function turnPage(direction) {
  220. const keyText = direction > 0 ? 'prev' : 'next';
  221. const link = getNode(keyText);
  222. if (!!link) {
  223. link.click()
  224. }
  225. }
  226.  
  227. if (top.location != self.location) return;
  228.  
  229. unsafeWindow.document.addEventListener('wheel', throttle((event) => {
  230. checkWheel(event)
  231. }, 1000));
  232.  
  233. if (needHotKey) {
  234. unsafeWindow.document.addEventListener("keydown", checkKey, false);
  235. }