📘极速翻页丨全端可用丨无动画全屏切换丨自定义悬浮窗丨黑色模式

👍忽略滑动动画,快速切换页面,可以通过键盘或浮动窗口进行操作,并调整黑色模式、悬浮窗大小和滑动保留页面比例。

Versión del día 29/5/2024. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name         📘极速翻页丨全端可用丨无动画全屏切换丨自定义悬浮窗丨黑色模式
// @version      2.1
// @description  👍忽略滑动动画,快速切换页面,可以通过键盘或浮动窗口进行操作,并调整黑色模式、悬浮窗大小和滑动保留页面比例。
// @author       Jingyu0123
// @match        *://*/*
// @license GPL
// @namespace https://greasyfork.org/users/1292046
// ==/UserScript==

(function () {
  "use strict";

  // 默认配置变量
  const autoDetectModeOptions = ["关闭", "同色", "反色"];
  var usePgUpPgDn = GM_getValue("usePgUpPgDn", true);
  var reservedHeightPercentage = GM_getValue("reservedHeightPercentage", 0);
  var isBlackMode = GM_getValue("isBlackMode", false);
  var floatWindowSize = GM_getValue("floatWindowSize", 80);
  var autoDetectMode = GM_getValue("autoDetectMode", "关闭");
  var isVerticalLayout = GM_getValue("isVerticalLayout", true);
  var windowOpacity = GM_getValue("windowOpacity", 0.8);
  var textOpacity = GM_getValue("textOpacity", 1.0);
  var floatWindowVisible = GM_getValue("floatWindowVisible", true);
  var upKey = GM_getValue("upKey", "PageUp");
  var downKey = GM_getValue("downKey", "PageDown");
  var enableHotkeys = GM_getValue("enableHotkeys", true);

  // 自动检测黑色模式
  function detectDarkMode() {
    const bgColor = window.getComputedStyle(document.body).backgroundColor;
    const colorValues = bgColor.match(/\d+/g);
    if (colorValues) {
      const r = parseInt(colorValues[0]);
      const g = parseInt(colorValues[1]);
      const b = parseInt(colorValues[2]);
      const brightness = (r * 299 + g * 587 + b * 114) / 1000;
      return brightness < 128;
    }
    return false;
  }

  if (autoDetectMode === "同色") {
    isBlackMode = detectDarkMode();
  } else if (autoDetectMode === "反色") {
    isBlackMode = !detectDarkMode();
  }

  // 创建悬浮窗
  function createFloatingWindow() {
    // 检查是否已经存在浮窗
    if (document.getElementById("floatingWindow")) {
      return; // 如果已经存在浮窗,则不创建新的浮窗
    }

    if (!floatWindowVisible) return;

    const floatWindow = document.createElement("div");
    floatWindow.id = "floatingWindow"; // 为浮窗设置一个唯一的ID
    const initialLeft = GM_getValue("floatWindowLeft", "calc(100% - 90px)");
    const initialTop = GM_getValue("floatWindowTop", "calc(100% - 270px)");
    floatWindow.style = `
        position: fixed;
        left: ${initialLeft};
        top: ${initialTop};
        width: ${isVerticalLayout ? floatWindowSize + "px" : floatWindowSize * 3 + "px"};
        height: ${isVerticalLayout ? floatWindowSize * 3 + "px" : floatWindowSize + "px"};
        background-color: ${isBlackMode ? "black" : "white"};
        color: ${isBlackMode ? "white" : "black"};
        border: 1px solid #ccc;
        z-index: 10000;
        display: flex;
        flex-direction: ${isVerticalLayout ? "column" : "row"};
        align-items: center;
        justify-content: space-around;
        cursor: move;
        padding: 5px;
        border-radius: 5px;
        opacity: ${windowOpacity};
    `;
    floatWindow.draggable = true;

    const btnSettings = document.createElement("button");
    btnSettings.innerHTML = "=";
    btnSettings.style = buttonStyle();
    btnSettings.onclick = showSettings;

    const btnDown = document.createElement("button");
    btnDown.innerHTML = "↓";
    btnDown.style = buttonStyle();
    btnDown.onclick = () => scrollPage(1);

    const btnUp = document.createElement("button");
    btnUp.innerHTML = "↑";
    btnUp.style = buttonStyle();
    btnUp.onclick = () => scrollPage(-1);

    floatWindow.appendChild(btnSettings);
    floatWindow.appendChild(btnUp);
    floatWindow.appendChild(btnDown);
    document.body.appendChild(floatWindow);

    // 悬浮窗拖拽功能
    floatWindow.addEventListener(
      "dragstart",
      (event) => {
        const style = window.getComputedStyle(event.target, null);
        event.dataTransfer.setData(
          "text/plain",
          parseInt(style.getPropertyValue("left"), 10) -
            event.clientX +
            "," +
            (parseInt(style.getPropertyValue("top"), 10) - event.clientY),
        );
      },
      false,
    );

    document.body.addEventListener(
      "dragover",
      (event) => {
        event.preventDefault();
        return false;
      },
      false,
    );

    document.body.addEventListener(
      "drop",
      (event) => {
        const offset = event.dataTransfer.getData("text/plain").split(",");
        const floatWindow = document.querySelector('div[draggable="true"]');
        const left = event.clientX + parseInt(offset[0], 10) + "px";
        const top = event.clientY + parseInt(offset[1], 10) + "px";
        floatWindow.style.left = left;
        floatWindow.style.top = top;
        GM_setValue("floatWindowLeft", left);
        GM_setValue("floatWindowTop", top);
        event.preventDefault();
        return false;
      },
      false,
    );
  }

  // 滚动页面函数
  function scrollPage(direction) {
    const screenHeight = window.innerHeight;
    const reservedHeight = (screenHeight * reservedHeightPercentage) / 100;
    window.scrollBy(0, direction * (screenHeight - reservedHeight));
  }

  // 键盘事件监听
  window.addEventListener("keydown", function (event) {
    if (!enableHotkeys) return; // 检查是否启用热键
    const screenHeight = window.innerHeight;
    const reservedHeight = (screenHeight * reservedHeightPercentage) / 100;
    if (event.key === upKey || event.key === downKey) {
      const direction = event.key === downKey ? 1 : -1;
      window.scrollBy(0, direction * (screenHeight - reservedHeight));
      event.preventDefault();
    }
  });

  // 显示设置窗口
  function showSettings() {
    const settingsWindow = document.createElement("div");
    settingsWindow.style = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 300px;
            background-color: ${isBlackMode ? "black" : "white"};
            color: ${isBlackMode ? "white" : "black"};
            border: 1px solid #ccc;
            z-index: 10001;
            padding: 20px;
            border-radius: 5px;
        `;

    const settingsContainer = document.createElement("div");
    settingsContainer.style = `
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            color: ${isBlackMode ? "white" : "black"};
        `;

    // 更新颜色
    const updateColors = () => {
      settingsWindow.style.backgroundColor = isBlackMode ? "black" : "white";
      settingsWindow.style.color = isBlackMode ? "white" : "black";
      settingsContainer.style.color = isBlackMode ? "white" : "black";
      saveButton.style.backgroundColor = isBlackMode ? "black" : "white";
      saveButton.style.color = isBlackMode ? "white" : "black";
      donateButton.style.backgroundColor = isBlackMode ? "black" : "white";
      donateButton.style.color = isBlackMode ? "white" : "black";
    };

    // 样式设置
    const createSetting = (labelText, value, onClick) => {
      const container = document.createElement("div");
      container.style = `
                display: flex;
                justify-content: space-between;
                width: 100%;
                margin-bottom: 10px;
                cursor: pointer;
            `;
      container.onclick = onClick;

      const label = document.createElement("span");
      label.innerText = labelText;
      container.appendChild(label);

      const indicator = document.createElement("span");
      indicator.innerText = value ? "✔️" : "✖️";
      container.appendChild(indicator);

      return container;
    };

    const modeSetting = createSetting("🌑黑色模式", isBlackMode, () => {
      isBlackMode = !isBlackMode;
      modeSetting.lastChild.innerText = isBlackMode ? "✔️" : "✖️";
      GM_setValue("isBlackMode", isBlackMode);
      updateColors();
    });

    const createAutoDetectSetting = (labelText, currentOption, optionValue) => {
      const container = document.createElement("div");
      container.style = `
                display: flex;
                justify-content: space-between;
                align-items: center;
                flex: 1;
                margin-bottom: 10px;
                cursor: pointer;
                padding-right: 10px;
            `;
      container.onclick = () => {
        autoDetectMode = optionValue;
        GM_setValue("autoDetectMode", autoDetectMode);
        updateAutoDetectSettings();
      };

      const label = document.createElement("span");
      label.innerText = labelText;
      container.appendChild(label);

      const indicator = document.createElement("span");
      indicator.innerText = currentOption === optionValue ? "✔️" : "✖️";
      container.appendChild(indicator);

      return container;
    };
    const updateAutoDetectSettings = () => {
      autoDetectSettingContainer.style = `
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        width: 100%;
    `;
      autoDetectSettingContainer.innerHTML = "";

      // 创建并定义解释文字的DOM元素
      const explanationText = document.createElement("div");
      explanationText.innerText = "🔎自动调整悬浮窗颜色(忽略上方修改)";
      explanationText.style =
        "margin-bottom: 10px; display: block; width: 100%; text-align: left;";
      autoDetectSettingContainer.appendChild(explanationText);

      // 创建自动检测设置按钮
      const settingsContainer = document.createElement("div");
      settingsContainer.style =
        "display: flex; justify-content: space-between; width: 80%;";
      settingsContainer.appendChild(
        createAutoDetectSetting("关闭", autoDetectMode, "关闭"),
      );
      settingsContainer.appendChild(
        createAutoDetectSetting("同色", autoDetectMode, "同色"),
      );
      settingsContainer.appendChild(
        createAutoDetectSetting("反色", autoDetectMode, "反色"),
      );

      autoDetectSettingContainer.appendChild(settingsContainer);
    };

    const layoutSetting = createSetting(
      "↕️竖向排列按钮",
      isVerticalLayout,
      () => {
        isVerticalLayout = !isVerticalLayout;
        layoutSetting.lastChild.innerText = isVerticalLayout ? "✔️" : "✖️";
      },
    );

    const createPercentageInput = (labelText, value, min, max) => {
      const container = document.createElement("div");
      container.style = `
                display: flex;
                justify-content: space-between;
                width: 100%;
                margin-bottom: 10px;
            `;

      const label = document.createElement("label");
      label.innerText = labelText;
      container.appendChild(label);

      const input = document.createElement("input");
      input.type = "number";
      input.value = value;
      input.min = min;
      input.max = max;
      input.style = `
                width: 50px;
                background-color: ${isBlackMode ? "black" : "white"};
                color: ${isBlackMode ? "white" : "black"};
            `;

      const percentageLabel = document.createElement("span");
      percentageLabel.innerText = "%";

      container.appendChild(input);
      container.appendChild(percentageLabel);

      return { container, input };
    };

    const sizeInput = createPercentageInput(
      "📏悬浮窗大小",
      floatWindowSize,
      20,
      100,
    );
    const percentageInput = createPercentageInput(
      "📄保留页面比例",
      reservedHeightPercentage,
      0,
      100,
    );
    const windowOpacityInput = createPercentageInput(
      "🌫️窗口透明度",
      windowOpacity * 100,
      20,
      100,
    );
    const textOpacityInput = createPercentageInput(
      "🖋️文字透明度",
      textOpacity * 100,
      20,
      100,
    );

    const saveButton = document.createElement("button");
    saveButton.innerHTML = "💾保存";
    saveButton.style = `
            margin-top: 10px;
            background-color: ${isBlackMode ? "black" : "white"};
            color: ${isBlackMode ? "white" : "black"};
        `;
    saveButton.onclick = () => {
      floatWindowSize = parseInt(sizeInput.input.value, 10);
      reservedHeightPercentage = Math.min(
        100,
        Math.max(0, parseInt(percentageInput.input.value, 10)),
      );
      windowOpacity = Math.min(
        1,
        Math.max(0.2, parseFloat(windowOpacityInput.input.value) / 100),
      );
      textOpacity = Math.min(
        1,
        Math.max(0.2, parseFloat(textOpacityInput.input.value) / 100),
      );
      GM_setValue("floatWindowSize", floatWindowSize);
      GM_setValue("isVerticalLayout", isVerticalLayout);
      GM_setValue("reservedHeightPercentage", reservedHeightPercentage);
      GM_setValue("windowOpacity", windowOpacity);
      GM_setValue("textOpacity", textOpacity);
      document.body.removeChild(settingsWindow);
      location.reload();
    };

    const donateButton = document.createElement("button");
    donateButton.innerHTML = "🔄更新/💰打赏";
    donateButton.style = `
            margin-top: 10px;
            background-color: ${isBlackMode ? "black" : "white"};
            color: ${isBlackMode ? "white" : "black"};
        `;
    donateButton.onclick = () => {
      if (
        confirm(
          "⚠️一部分脚本管理器无法自动检测脚本是否有更新,所以需要你去看一下。\n🔄现在的版本号是:v2.1。\n💬如果脚本有任何问题,或者你有任何创意,都可以在脚本反馈区留言。\n📄在脚本发布页的介绍部分也放置了我的打赏二维码,\n🥤如果这个脚本帮你解决了大问题,希望你能请我喝杯奶茶哦!",
        )
      ) {
        window.open("https://greasyfork.org/zh-CN/scripts/493257", "_blank");
      }
    };

    const toggleFloatWindow = () => {
      floatWindowVisible = !floatWindowVisible;
      GM_setValue("floatWindowVisible", floatWindowVisible);
      document.body.removeChild(settingsWindow);
      location.reload();
    };

    const confirmToggleFloatWindow = () => {
      if (
        confirm(
          "❗️确定要关闭悬浮窗吗?\n🖥️电脑端可以通过脚本管理器中的选项重新打开,\n📱若脚本管理器没有图形化界面,可以重新安装脚本。",
        )
      ) {
        toggleFloatWindow();
      }
    };

    const floatWindowSetting = createSetting(
      "📴关闭悬浮窗(长按悬浮窗可以移动)",
      false,
      confirmToggleFloatWindow,
    );

    // 分割线
    const createSeparator = () => {
      const separator = document.createElement("hr");
      separator.style =
        "width: 100%; margin: 10px 0; border: none; border-top: 1px solid #ccc;";
      return separator;
    };

    settingsContainer.appendChild(modeSetting);

    const hotkeysToggleSetting = createSetting(
      "开启翻页热键功能",
      enableHotkeys,
      () => {
        enableHotkeys = !enableHotkeys;
        hotkeysToggleSetting.lastChild.innerText = enableHotkeys ? "✔️" : "✖️";
        GM_setValue("enableHotkeys", enableHotkeys);
      },
    );

    const autoDetectSettingContainer = document.createElement("div");
    settingsContainer.appendChild(autoDetectSettingContainer);
    updateAutoDetectSettings();

    settingsContainer.appendChild(createSeparator());

    settingsContainer.appendChild(layoutSetting);
    settingsContainer.appendChild(sizeInput.container);
    settingsContainer.appendChild(percentageInput.container);
    settingsContainer.appendChild(windowOpacityInput.container);
    settingsContainer.appendChild(textOpacityInput.container);
    settingsContainer.appendChild(floatWindowSetting);

    settingsContainer.appendChild(createSeparator());

    const hotkeySetting = createSetting("🎛️设置电脑端翻页热键", false, () => {
      const hotkeyWindow = document.createElement("div");
      hotkeyWindow.style = `
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 300px;
        background-color: ${isBlackMode ? "black" : "white"};
        color: ${isBlackMode ? "white" : "black"};
        border: 1px solid #ccc;
        z-index: 10002;
        padding: 20px;
        border-radius: 5px;
    `;

      const hotkeyContainer = document.createElement("div");
      hotkeyContainer.style = `
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        color: ${isBlackMode ? "white" : "black"};
    `;

      // 创建并定义解释文字的DOM元素
      const explanationText = document.createElement("div");
      explanationText.innerText =
        "💬请设置翻页热键\n💡选中文本框后直接按下目标热键试试吧:";
      explanationText.style =
        "margin-bottom: 10px; display: block; width: 100%;";
      hotkeyWindow.appendChild(explanationText);

      const createHotkeySetting = (labelText, currentKey, onKeyChange) => {
        const container = document.createElement("div");
        container.style = `
            display: flex;
            justify-content: space-between;
            width: 100%;
            margin-bottom: 10px;
        `;

        const label = document.createElement("label");
        label.innerText = labelText;
        container.appendChild(label);

        const input = document.createElement("input");
        input.type = "text";
        input.value = currentKey;
        input.style = `
            width: 100px;
            background-color: ${isBlackMode ? "black" : "white"};
            color: ${isBlackMode ? "white" : "black"};
        `;
        input.onkeydown = (event) => {
          event.preventDefault();
          input.value = event.key;
          onKeyChange(event.key);
        };

        container.appendChild(input);
        return container;
      };

      const upKeySetting = createHotkeySetting("上翻页热键", upKey, (key) => {
        upKey = key;
      });
      const downKeySetting = createHotkeySetting(
        "下翻页热键",
        downKey,
        (key) => {
          downKey = key;
        },
      );

      const hotkeySaveButton = document.createElement("button");
      hotkeySaveButton.innerHTML = "💾保存";
      hotkeySaveButton.style = `
        margin-top: 10px;
        background-color: ${isBlackMode ? "black" : "white"};
        color: ${isBlackMode ? "white" : "black"};
    `;
      hotkeySaveButton.onclick = () => {
        GM_setValue("upKey", upKey);
        GM_setValue("downKey", downKey);
        document.body.removeChild(hotkeyWindow);
      };

      hotkeyContainer.appendChild(upKeySetting);
      hotkeyContainer.appendChild(downKeySetting);
      hotkeyContainer.appendChild(hotkeySaveButton);

      hotkeyWindow.appendChild(hotkeyContainer);
      document.body.appendChild(hotkeyWindow);
    });

    settingsContainer.appendChild(hotkeysToggleSetting);
    settingsContainer.appendChild(hotkeySetting);

    // 创建按钮容器
    const buttonContainer = document.createElement("div");
    buttonContainer.style = `
    display: flex;
    justify-content: space-between;
    width: 100%;
    margin-top: 10px;
`;

    // 修改保存按钮的样式并添加到按钮容器中
    saveButton.style = `
    flex: 1;
    margin-right: 10px;
    background-color: ${isBlackMode ? "black" : "white"};
    color: ${isBlackMode ? "white" : "black"};
`;
    buttonContainer.appendChild(saveButton);

    // 修改检查更新/打赏按钮的样式并添加到按钮容器中
    donateButton.style = `
    flex: 1;
    background-color: ${isBlackMode ? "black" : "white"};
    color: ${isBlackMode ? "white" : "black"};
`;
    buttonContainer.appendChild(donateButton);

    // 将按钮容器添加到设置窗口中
    settingsContainer.appendChild(buttonContainer);

    settingsWindow.appendChild(settingsContainer);
    document.body.appendChild(settingsWindow);

    updateColors();
  }

  // 创建并显示悬浮窗
  createFloatingWindow();

  // 注册菜单命令
  GM_registerMenuCommand("显示悬浮窗", function () {
    GM_setValue("floatWindowVisible", true);
    location.reload();
  });

  // 样式辅助函数
  function buttonStyle() {
    return `
            width: ${floatWindowSize}px;
            height: ${floatWindowSize}px;
            font-size: 20px;
            background-color: ${isBlackMode ? "black" : "white"};
            color: ${isBlackMode ? "white" : "black"};
            border: none;
            opacity: ${textOpacity};
        `;
  }
})();