Greasy Fork

来自缓存

Greasy Fork is available in English.

平滑滚动翻页

使用上下键或WS键进行平滑滚动翻页,一次翻页0.9倍页面距离。键盘左右键或A/D键模拟点击页面中【上一页|上一章】【下一页|下一章】按钮,双击只点击【上一章】【下一章】按钮。可以与自动滚屏助手脚本联动。适配开源阅读的web端翻页。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         平滑滚动翻页
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  使用上下键或WS键进行平滑滚动翻页,一次翻页0.9倍页面距离。键盘左右键或A/D键模拟点击页面中【上一页|上一章】【下一页|下一章】按钮,双击只点击【上一章】【下一章】按钮。可以与自动滚屏助手脚本联动。适配开源阅读的web端翻页。
// @author       coccvo
// @include      *
// @exclude      https://www.bilibili.com/*
// @grant        none
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAVdJREFUWEftlj1Ow0AQhd9YWKKmy3b8FPSpiKUojXMaKOAg0HAZCEUktB0HoMDu4twBkwxaLEvG7C7GzMoowvV437dvfnYIA3/k00+TgiX4Flo5df4BfuVABEx5yxuOSPtSFSQFRvxOq0cjPD9bTXwQ4gBN8frmPghRAJv4dxCCAPRCtH325Zs5OgX4pBkjCNBvKuwmAAMZgFvjSQQ6Z/Cxyx9xB8p4FC+X9NYUnM14Ly7XpQ1CFMAmXou6IMQACJTd69GnCm/fOE3WGcBHQbqAgasHrW58vTBPiksGrncTAEC+0MpZ7ebWaVLkAA6DOGAO9RXhePwUH+yr13aKxIqwPtgG4RI3/4gDVCCUE/hjEDFw0bY9WAr6vAaBHOiOIgjwB55j70IyXU1483U/FHSgst26kjnEg3XBoEtpXX6DruXdeyDYIOqOIF6E3aWryN4APxXqE/8OqY/0IehdmXQAAAAASUVORK5CYII=
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // ==============================
    // 全局状态管理
    // ==============================
    let isScrollKeyPressed = false;// 用户是否正在按 W/S 键滚动
    let autoScrollWasActive = false;// 记录自动滚屏是否曾运行(用于恢复)
    let scrollDistance = window.innerHeight * 0.9; // 每次手动滚动的距离(视口高度的 90%)
    let clickTimer = null;// 用于实现“单击 vs 双击”逻辑的定时器
    const kaiyuanCooldown = 600; // 开源阅读专用冷却时间(毫秒)
    let lastKaiyuanClick = 0;

    // ==============================
    // 页面类型识别
    // ==============================
    function isKaiyuanReaderChapterPage() {
        try {
            return window.location.pathname.endsWith('/vue/index.html') &&
                window.location.hash.startsWith('#/chapter');
        } catch (e) {
            return false;
        }
    }// 判断当前页面是否为「开源阅读 Web 端」的阅读页

    // ==============================
    // 与「自动滚屏助手」脚本的协同
    // ==============================
    const hasAutoScrollAssistant = () =>
    typeof window.getAutoScrollState === 'function';

    // ==============================
    // 通用页面:通过文本查找并点击导航按钮
    // ==============================
    function clickElementByText(textContent) {
        const clickableElements = document.querySelectorAll(
            'a, button, [role="button"], span[onclick], div[onclick]'
        );
        for (const el of clickableElements) {
            if (el.textContent.trim() === textContent) {
                el.click();
                return true;
            }
        }
        return false;
    }

    // ==============================
    // 开源阅读 Web 端:通过图标查找章节切换按钮
    // ==============================
    function findChapterButtonByIcon(iconText) {
        const icons = document.querySelectorAll('.tool-icon .iconfont');
        for (const icon of icons) {
            if (icon.textContent.includes(iconText)) {
                return icon.parentElement;
            }
        }
        return null;
    }

    function triggerToolBarClick() {
        const toolBar = document.querySelector('.tool-bar');
        if (toolBar) toolBar.click();
    }//竖屏翻页时关闭菜单

    // ==============================
    // 导航处理:统一翻页入口(A/D/←/→)
    // ==============================
    function handleNavigation(direction) {
        const now = Date.now();

        // ── 1. 开源阅读 Web 端:带 cooldown 的图标点击 ──
        if (isKaiyuanReaderChapterPage()) {
            if (now - lastKaiyuanClick < kaiyuanCooldown) {
                return; // 防抖:避免快速连点
            }

            const iconMap = { prev: '', next: '' };
            const button = findChapterButtonByIcon(iconMap[direction]);
            if (button) {
                button.click();
                triggerToolBarClick();
                document.body.focus();
                lastKaiyuanClick = now; // 更新冷却时间
            }
            return;
        }

        // ── 2. 其他网站:纯双击逻辑,无 cooldown 干扰 ──
        if (clickTimer === null) {
            // 第一次点击:延迟执行“翻页”
            clickTimer = setTimeout(() => {
                const pageTexts = direction === 'prev'
                ? ['上一页', '<上一页']
                : ['下一页', '下一页>'];

                let clicked = false;
                for (const text of pageTexts) {
                    if (clickElementByText(text)) {
                        clicked = true;
                        break;
                    }
                }
                if (!clicked) {
                    // 降级:尝试“跳章”
                    clickElementByText(direction === 'prev' ? '上一章' : '下一章');
                }
                document.body.focus();
                clickTimer = null;
            }, 300); // 双击判定窗口
        } else {
            // 快速第二次点击:取消翻页,立即跳章
            clearTimeout(clickTimer);
            clickTimer = null;
            clickElementByText(direction === 'prev' ? '上一章' : '下一章');
            document.body.focus();
        }
    }

    // ==============================
    // 手动滚动处理(W/S/↑/↓)
    // ==============================
    /**
     * 执行一次视口比例的平滑滚动,并与自动滚屏助手协同。
     * @param {boolean} isDown - 是否向下滚动
     */
    function performManualScroll(isDown) {
        // 若自动滚屏助手正在运行,先暂停它
        if (hasAutoScrollAssistant() && window.getAutoScrollState()) {
            autoScrollWasActive = true;
            window.turnOffAutoScroll();
        }

        const distance = isDown ? scrollDistance : -scrollDistance;
        window.scrollBy({ top: distance, left: 0, behavior: 'smooth' });
    }

    // ==============================
    // 键盘事件监听
    // ==============================
    function onKeyDown(event) {
        // 忽略组合键(Ctrl/Alt/Shift + 其他)
        if (event.ctrlKey || event.altKey || event.shiftKey) return;

        const target = event.target;
        // 在输入框、文本域或可编辑区域中不拦截快捷键
        if (
            target.tagName === 'INPUT' ||
            target.tagName === 'TEXTAREA' ||
            target.isContentEditable ||
            target.closest('[contenteditable="true"]')
        ) return;

        const key = event.key.toLowerCase();
        let handled = false;

        // --- 手动滚动:W / S / ↑ / ↓ ---
        if (['w', 'arrowup'].includes(key)) {
            event.preventDefault();
            performManualScroll(false);
            isScrollKeyPressed = true;
            handled = true;
        } else if (['s', 'arrowdown'].includes(key)) {
            event.preventDefault();
            performManualScroll(true);
            isScrollKeyPressed = true;
            handled = true;
        }

        // --- 页面/章节导航:A / D / ← / → ---
        if (key === 'a' || key === 'arrowleft') {
            event.preventDefault();
            handleNavigation('prev');
            handled = true;
        } else if (key === 'd' || key === 'arrowright') {
            event.preventDefault();
            handleNavigation('next');
            handled = true;
        }
    }

    function onKeyUp(event) {
        const key = event.key.toLowerCase();
        // 松开滚动键后,延迟恢复自动滚屏(如果之前被暂停)
        if (['w', 's', 'arrowup', 'arrowdown'].includes(key)) {
            isScrollKeyPressed = false;
            if (autoScrollWasActive) {
                setTimeout(() => {
                    if (!isScrollKeyPressed && hasAutoScrollAssistant()) {
                        window.startAutoScroll?.();
                        autoScrollWasActive = false;
                    }
                }, 500);
            }
        }
    }

    // ==============================
    // 初始化
    // ==============================
    function init() {
        scrollDistance = window.innerHeight * 0.9;

        // 监听键盘事件
        document.addEventListener('keydown', onKeyDown, { passive: false });
        document.addEventListener('keyup', onKeyUp, { passive: false });

        // 响应窗口尺寸变化
        window.addEventListener('resize', () => {
            scrollDistance = window.innerHeight * 0.9;
        });
    }

    // 等待 DOM 加载完成
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();