微信读书自动翻滚

操作手册:①空格启停滚动;②A减速,D加速;③点击重置恢复速度为“2”。基于@yuankaiyu “微信懒人翻书”制作,修改内容:①使用新的滚动逻辑,可以在自动翻滚的同时手动翻滚;②调整操作台样式,新增交互效果;③新增速度记忆功能。

// ==UserScript==
// @name         微信读书自动翻滚
// @namespace    http://tampermonkey.net/
// @version      20250311
// @description  操作手册:①空格启停滚动;②A减速,D加速;③点击重置恢复速度为“2”。基于@yuankaiyu “微信懒人翻书”制作,修改内容:①使用新的滚动逻辑,可以在自动翻滚的同时手动翻滚;②调整操作台样式,新增交互效果;③新增速度记忆功能。
// @author       EuSky
// @match        https://weread.qq.com/web/reader/*
// @icon         https://weread.qq.com/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// @updateURL
// ==/UserScript==

const primaryColor = "linear-gradient(45deg, rgb(44, 106, 255),rgb(48, 173, 254))";

// 创建带样式的按钮(新增美化样式)
const createBtn = (innerText, id, style, fn) => {
    const btn = document.createElement("button");
    btn.innerText = innerText;
    btn.id = id;
    // 样式优化:圆形按钮/悬停效果/居中
    btn.style.cssText = `
        margin: 0 3px;
        cursor: pointer;
        color: white;
        background: transparent;
        border: none;
        font-size: 16px;
        line-height: 1;
        width: 24px;
        height: 24px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 12px;
        transition: all 0.2s;
    `;
    // 交互效果:悬停和点击动画
    btn.addEventListener('mouseover', () => btn.style.background = 'rgba(255,255,255,0.1)');
    btn.addEventListener('mouseout', () => btn.style.background = 'transparent');
    btn.addEventListener('mousedown', () => btn.style.transform = 'scale(0.9)');
    btn.addEventListener('mouseup', () => btn.style.transform = 'scale(1)');
    btn.addEventListener('click', fn);
    return btn;
};

// 在加减速函数中限制最小值
const minSpeed = 1.1;

const minus = (speedEle) => {
    let speed = +speedEle.innerText;
    speed = Math.max(minSpeed, speed - 0.1);
    speedEle.innerText = speed.toFixed(1);
    GM_setValue("speed", speed);
};

const add = (speedEle) => {
    let speed = +speedEle.innerText;
    speed += 0.1;
    speedEle.innerText = speed.toFixed(1);
    GM_setValue("speed", speed);
};

(function () {
    "use strict";
    // 创建主容器(样式优化:阴影/圆角/最小宽度)
    const createBox = () => {
        const box = document.createElement("div");
        box.style.cssText = `
            position: fixed;
            top: 20px;
            left: 100px;
            font-size: 16px;
            background: ${primaryColor};
            padding: 4px 0;
            border-radius: 8px;
            box-shadow: 4px 4px 20px rgba(0, 0, 0, 0.4);
            cursor: grab;
            color: white;
            min-width: 100px;
            display: flex;
            flex-direction: column;
            align-items: center;
        `;
        return box;
    };

    // 创建翻书按钮(居中对齐)
    const createHeader = () => {
        const divEle = document.createElement("div");
        divEle.style.cssText = `
            cursor: pointer;
            width: 50%;
            font-size: 16px;
            text-align: center;
            padding: 2px 0;
            transition: background 0.2s;
            border-radius: 4px;
        `;
        divEle.textContent = "翻书";
        // 翻书按钮交互效果
        divEle.addEventListener('mouseover', () => divEle.style.background = 'rgba(255,255,255,0.1)');
        divEle.addEventListener('mouseout', () => divEle.style.background = 'transparent');
        divEle.addEventListener("click", scrollAuto);
        return divEle;
    };

    const box = createBox();
    const header = createHeader();
    box.appendChild(header);

    // 创建内容区域(Flex纵向布局)
    const createContentArea = () => {
        const div = document.createElement("div");
        div.style.cssText = `
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 4px 6px;
        `;
        return div;
    };

    const contentArea = createContentArea();
    box.appendChild(contentArea);

    // 创建速度控制器(水平居中布局)
    const createSpeedController = () => {
        const speedController = document.createElement("div");
        speedController.style.cssText = `
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 2px 0;
        `;

        const speedEle = document.createElement("div");
        speedEle.id = "speed";
        const initialSpeed = GM_getValue("speed", 2).toFixed(1);
        speedEle.textContent = initialSpeed;
        // 速度显示样式优化
        speedEle.style.cssText = `
            width: 20px;
            text-align: center;
            font-weight: bold;
            font-size: 16px;
            margin: 0 6px;
            letter-spacing: -1px; // 补偿字体间距
            transform: translateX(-0px); // 微调位置
        `;

        speedController.appendChild(createBtn("-", "minusBtn", "", () => minus(speedEle)));
        speedController.appendChild(speedEle);
        speedController.appendChild(createBtn("+", "addBtn", "", () => add(speedEle)));
        return { speedEle, speedController };
    };

    const { speedEle, speedController } = createSpeedController();
    contentArea.appendChild(speedController);

    // 创建重置按钮(全宽居中样式)
    const hint1 = document.createElement("div");
    hint1.textContent = "重置";
    hint1.style.cssText = `
        cursor: pointer;
        width: 54%;
        text-align: center;
        padding: 2px 0;
        font-size: 16px;
        transition: background 0.2s;
        border-radius: 4px;
    `;
    // 重置按钮交互效果
    hint1.addEventListener('mouseover', () => hint1.style.background = 'rgba(255,255,255,0.1)');
    hint1.addEventListener('mouseout', () => hint1.style.background = 'transparent');
    hint1.addEventListener('click', () => {
        speedEle.innerText = '2.0';
        GM_setValue("speed", 2);
    });
    contentArea.appendChild(hint1);

    document.body.appendChild(box);

    // 自动滚动控制逻辑
    let animationFrameId;
    let fractionalY = 0;

    // 核心滚动逻辑
    function scrollAuto() {
        if (animationFrameId) {
            cancelAnimationFrame(animationFrameId);
            animationFrameId = null;
        } else {
            animationFrameId = requestAnimationFrame(smoothScroll);
        }
    }

    function smoothScroll() {
        if (isScrolledToBottom()) {
            cancelAnimationFrame(animationFrameId);
            animationFrameId = null;
            fractionalY = 0;
            return;
        }

        const scrollDistance = +speedEle.innerText;
        fractionalY += scrollDistance;

        const delta = Math.floor(fractionalY);
        if (delta !== 0) {
            window.scrollBy(0, delta);
            fractionalY -= delta;
        }

        animationFrameId = requestAnimationFrame(smoothScroll);
    }

    function isScrolledToBottom() {
        return window.scrollY + window.innerHeight >= document.body.scrollHeight - 5;
    }

    // 事件监听器(保留原有功能)
    document.addEventListener("keydown", function (event) {
        // 空格键控制启停
        if (event.key === " " || event.keyCode === 32) {
            event.preventDefault();
            scrollAuto();
        }
        // A/D 键控制速度
        if (event.key === "d" || event.key === "D") add(speedEle);
        if (event.key === "a" || event.key === "A") minus(speedEle);
    });

    // 拖拽功能(保留原有实现)
    const dragHandle = () => {
        const draggable = box;
        let offsetX, offsetY, drag = false;
        draggable.onmousedown = (e) => {
            offsetX = e.clientX - draggable.getBoundingClientRect().left;
            offsetY = e.clientY - draggable.getBoundingClientRect().top;
            drag = true;
        };
        document.onmousemove = (e) => {
            if (drag) {
                draggable.style.left = `${e.clientX - offsetX}px`;
                draggable.style.top = `${e.clientY - offsetY}px`;
            }
        };
        document.onmouseup = () => drag = false;
    };
    dragHandle();
})();