自动滚屏助手

一个带有开关控制的自动滚屏助手,按 ESC 暂停/继续, 上/下方向键跳跃, <小于号键/>大于号键调速。左右方向键未设置,避免与翻页冲突

// ==UserScript==
// @name         自动滚屏助手
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  一个带有开关控制的自动滚屏助手,按 ESC 暂停/继续, 上/下方向键跳跃, <小于号键/>大于号键调速。左右方向键未设置,避免与翻页冲突
// @author       coccvo
// @match        *://*/*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAeBJREFUWEftl1lKxEAURU87oB/qFhxAxQHB7TigbqN//NJlOKMLcsIBpy3oh7P2hVdShqSqkg6I0PWTpqty38nLe1U3Df54NCrG7wVGAV013oBbu5aSLAMwDJwAA5EIT8A8cJ1CkgqgwDOe4BdwBbzbfz3AOODrXQKTMYgUgGegz4RWgL2IqNbs2JoXoD+0PgZwBkwBeuKu2NNk5j8tI9Lws/drWQhgxApLN8RAi9gEriGt+7xFIeFHK7iUtBcBrALbgLSGygI4+hDkhhXieuD1BHWKxNXfrwnvPgXS1YI0Xdf88BYBTAAXQKyVUgCkIz21pPSSilBVq94PVrBlKFakp8A0MAvodwegk4H/nYFuw/+wa7YNs/NaVmsXZPd2H0B+4c7A/P2lVoD9ltFYsiByRHJBGgruDpvDlldY9F50rQDSlR9YLtj7j1q73UJmrhJAbCv2M+HiZZ/c/V9pK045jA68VBcFF0Slw0g3phw0m/aYzbqPY+k5QyJTsRsIEJpaA7aqGpI6LZm65CGPNOb1XAW3Y0qloaM4d8QAdJNvy5VSZ7mLNJ0P1HzbttwFqfJhcm5GJFg+KRlwAnqPx8BgpCBVvHPetlwbgC+kT7GxzMfpTZ7pjHVPmQzEtCrNfwMMv4whwzA6nAAAAABJRU5ErkJggg==
// @license MIT
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_notification
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 滚动控制变量
    let isEnabled = false;
    let isScrolling = false;
    let animationFrameId;
    let lastTimestamp = 0;
    let pixelsPerSecond = 70;
    const speedAdjustAmount = 3;
    const jumpIncrement = 200;
    const maxPixelsPerSecond = 800;
    const minPixelsPerSecond = 59;
    const pressedKeys = new Set();

    // 菜单命令ID
    let enableCommandId, disableCommandId;

    // 初始化菜单
    function initMenu() {
        // 注册"启用自动滚屏"命令
        enableCommandId = GM_registerMenuCommand("▶ 启用自动滚屏", function() {
            enableAutoScroll();
        });

        // 注册"禁用自动滚屏"命令(初始时禁用)
        disableCommandId = GM_registerMenuCommand("⏸ 禁用自动滚屏", function() {
            disableAutoScroll();
        }, { autoClose: true });

        // 初始状态禁用"禁用"命令
        GM_unregisterMenuCommand(disableCommandId);
    }

    // 启用自动滚屏
    function enableAutoScroll() {
        if (isEnabled) return;

        isEnabled = true;
        initAutoScroll();
        startRAFScroll();

        // 更新菜单状态
        GM_unregisterMenuCommand(enableCommandId);
        disableCommandId = GM_registerMenuCommand("⏸ 禁用自动滚屏", function() {
            disableAutoScroll();
        }, { autoClose: true });

        GM_notification({
            text: "自动滚屏已启用\n按ESC控制暂停/继续\n方向键跳跃\n<键/>键调速",
            title: "自动滚屏助手",
            timeout: 3000
        });
    }

    // 禁用自动滚屏
    function disableAutoScroll() {
        if (!isEnabled) return;

        isEnabled = false;
        turnOff();
        removeEventListeners();

        // 更新菜单状态
        GM_unregisterMenuCommand(disableCommandId);
        enableCommandId = GM_registerMenuCommand("▶ 启用自动滚屏", function() {
            enableAutoScroll();
        });

        GM_notification({
            text: "自动滚屏已禁用",
            title: "自动滚屏助手",
            timeout: 2000
        });
    }

    // 滚动逻辑
    function scrollStepRAF(timestamp) {
        if (!isScrolling || !isEnabled) return;

        if (lastTimestamp === 0) {
            lastTimestamp = timestamp;
            animationFrameId = requestAnimationFrame(scrollStepRAF);
            return;
        }

        const deltaTime = (timestamp - lastTimestamp) / 1000;
        lastTimestamp = timestamp;
        const scrollAmount = pixelsPerSecond * deltaTime;
        window.scrollBy(0, scrollAmount);
        animationFrameId = requestAnimationFrame(scrollStepRAF);
    }

    function startRAFScroll() {
        if ((isScrolling && animationFrameId) || !isEnabled) return;
        isScrolling = true;
        lastTimestamp = 0;
        animationFrameId = requestAnimationFrame(scrollStepRAF);
    }

    function turnOff() {
        if (!isScrolling) return;
        isScrolling = false;
        if (animationFrameId) {
            cancelAnimationFrame(animationFrameId);
            animationFrameId = null;
        }
    }

    function handleKeyDown(event) {
        if (!isEnabled) return;

        const targetElement = event.target;
        const isInInputElement = targetElement.tagName === 'INPUT' ||
                               targetElement.tagName === 'TEXTAREA' ||
                               targetElement.tagName === 'SELECT' ||
                               targetElement.isContentEditable;
        if (isInInputElement && event.key !== 'Escape') {
            return;
        }

        pressedKeys.add(event.key);
        let handled = false;

        if (pressedKeys.has('Escape')) {
            if (isScrolling) {
                turnOff();
                console.log("自动滚屏: 已暂停 (ESC)");
            } else {
                startRAFScroll();
                console.log("自动滚屏: 已启动 (ESC)");
            }
            handled = true;
        }
        if (pressedKeys.has('ArrowUp')) {
            window.scrollBy(0, -jumpIncrement);
            handled = true;
        }
        if (pressedKeys.has('ArrowDown')) {
            window.scrollBy(0, jumpIncrement);
            handled = true;
        }
        if (pressedKeys.has('.')) {
            pixelsPerSecond += speedAdjustAmount;
            if (pixelsPerSecond > maxPixelsPerSecond) pixelsPerSecond = maxPixelsPerSecond;
            console.log("自动滚屏: 速度", pixelsPerSecond, "px/s");
            handled = true;
        }
        if (pressedKeys.has(',')) {
            pixelsPerSecond -= speedAdjustAmount;
            if (pixelsPerSecond < minPixelsPerSecond) pixelsPerSecond = minPixelsPerSecond;
            console.log("自动滚屏: 速度", pixelsPerSecond, "px/s");
            handled = true;
        }

        if (handled) {
            event.preventDefault();
        }
    }

    function handleKeyUp(event) {
        pressedKeys.delete(event.key);
    }

    function initAutoScroll() {
        window.addEventListener('keydown', handleKeyDown);
        window.addEventListener('keyup', handleKeyUp, false);
        window.turnOffAutoScroll_A_RAF = turnOff;
        window.startAutoScroll_A_RAF = startRAFScroll;
    }

    function removeEventListeners() {
        window.removeEventListener('keydown', handleKeyDown);
        window.removeEventListener('keyup', handleKeyUp);
    }

    // 初始化菜单
    initMenu();
})();