Greasy Fork is available in English.

Side Scroll Wheel Pagination

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

// ==UserScript==
// @name         Side Scroll Wheel Pagination
// @namespace    Hal74
// @version      0.1
// @description  鼠标侧滚轮翻页,支持键盘热键翻页
// @author       Hal74
// @match        *://*/*
// @license        MIT
// ==/UserScript==

// 热键翻页开关
const needHotKey = false;
// 编辑下面的数组来自定义规则
const specialXpaths = [
  {
      //匹配的url
      urls: ["taobao.com"],
      //上一页节点的xpath
      prev: '//button[contains(@aria-label,"上一页")]',
      //下一页节点的xpath
      next: '//button[contains(@aria-label,"下一页")]',
  },
  // elementUI
  {
      urls: ["localhost"],
      prev: '//button[@class="btn-prev"]',
      next: '//button[@class="btn-next"]',
  },
];

const generalXpaths = [
  ["//a[(text()='", "')]"],
  ["//a[@class='", "']"],
  ["//button[(text()='", "')]"],
  ["//button[@class='", "']"],
  ["//input[@type='button' and @value='", "']"],
];

const Strs = {
  next: [
      "下一页",
      "下页",
      "下一页 »",
      "下一页 >",
      "下一节",
      "下一章",
      "下一篇",
      "后一章",
      "后一篇",
      "后页>",
      "»",
      "next",
      "next page",
      "old",
      "older",
      "earlier",
      "下頁",
      "下一頁",
      "后一页",
      "后一頁",
      "翻下页",
      "翻下頁",
      "后页",
      "后頁",
      "下翻",
      "下一个",
      "下一张",
      "下一幅",
  ],
  prev: [
      "上一页",
      "上页",
      "« 上一页",
      "< 上一页",
      "上一节",
      "上一章",
      "上一篇",
      "前一章",
      "前一篇",
      "<前页",
      "«",
      "previous",
      "prev",
      "previous page",
      "new",
      "newer",
      "later",
      "上頁",
      "上一頁",
      "前一页",
      "前一頁",
      "翻上页",
      "翻上頁",
      "前页",
      "前頁",
      "上翻",
      "上一个",
      "上一张",
      "上一幅",
  ]
}

const keys = {
  prev: [
      'ArrowLeft',
      'a',
  ],
  next: [
      'ArrowRight',
      'd'
  ]
}

function checkTextArea(node) {
  var name = node.localName.toLowerCase();
  if (name === "textarea" || name === "input" || name === "select") {
    return true;
  }
  if (name === "div" && (node.id.toLowerCase().includes("textarea") || node.contentEditable !== 'inherit')) {
    return true;
  }
  return false;
}

function hasHorizontalScrollbar(node) {
  while (node) {
    if ((node.scrollWidth - node.clientWidth) > 20) {
      return true;
    }
    node = node.parentNode;
  }
  return false;
}


function xpath(query) {
  return unsafeWindow.document.evaluate(
    query,
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null
  );
}

function getNode(lnstr) {
  var node = getNodeByGeneralXpath(lnstr);
  if (!node) node = getNodeBySpecialXpath(lnstr);
  return node;
}

function getNodeByGeneralXpath(lnstr) {
  var strs;
  strs = Strs[lnstr];
  var x = generalXpaths;
  for (var i in x) {
    for (var j in strs) {
      var query = x[i][0] + strs[j] + x[i][1];
      var nodes = xpath(query);
      if (nodes.snapshotLength > 0) return nodes.snapshotItem(0);
    }
  }
  return null;
}

function getNodeBySpecialXpath(lnstr) {
  var s = specialXpaths;
  for (var i in s) {
    if (checkXpathUrl(s[i].urls)) {
      return xpath(s[i][lnstr]).snapshotItem(0);
    }
  }
  return null;
}

function checkXpathUrl(urls) {
  for (var i in urls) if (location.href.indexOf(urls[i]) >= 0) return true;
  return false;
}

function throttle(func, delay) {
  let lastCall = 0;
  return function(...args) {
    const now = new Date().getTime();
    if (now - lastCall < delay) {
      return;
    }
    lastCall = now;
    return func(...args);
  };
}

function findParentKey(obj, value) {
  for (let key in obj) {
    if (obj[key].includes(value)) {
      return key;
    }
  }
  return null;
}

function checkKey(e) {
  if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
  if (checkTextArea(e.target)) return;
  const key = findParentKey(keys, e.key)
  const link = getNode(key);
  if (key && !!link) {
    link.click()
  }
}

function checkWheel(e) {
  if (hasHorizontalScrollbar(e.target)) return
  if (event.deltaX !== 0) {
    turnPage(event.deltaX);
  }
}

function turnPage(direction) {
  const keyText = direction > 0 ? 'prev' : 'next';
  const link = getNode(keyText);
  if (!!link) {
    link.click()
  }
}

if (top.location != self.location) return;

unsafeWindow.document.addEventListener('wheel', throttle((event) => {
  checkWheel(event)
}, 1000));

if (needHotKey) {
  unsafeWindow.document.addEventListener("keydown", checkKey, false);
}