Greasy Fork

Greasy Fork is available in English.

墨水屏电纸书划动优化

消除划动动画 可设置划动倍率和更新间隔 可设置双指放大双击复原

当前为 2025-10-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         墨水屏电纸书划动优化
// @namespace    cc.cxuan.books
// @version      1.26
// @description  消除划动动画  可设置划动倍率和更新间隔 可设置双指放大双击复原
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @noframes
// @license      MIT
// @author       cxuan.cc
// ==/UserScript==

(function(){
    'use strict';

    //设置项读取
    let mx = GM_getValue('multiplierX', 5);
    let my = GM_getValue('multiplierY', 2);
    let intervalSec = GM_getValue('interval', 0);
    let enableZoomAndReset = GM_getValue('enableZoomAndReset', true);

    //注入/替换 viewport meta 来开启或禁止双指缩放
    const applyViewport = ()=>{
        let head = document.head || document.getElementsByTagName('head')[0] || document.documentElement;
        // 删除旧的 viewport
        Array.from(document.querySelectorAll('meta[name=viewport]')).forEach(m=>m.remove());
        // 添加新的
        const meta = document.createElement('meta');
        meta.name = 'viewport';
        if(enableZoomAndReset){
            meta.content = 'width=device-width, initial-scale=1, minimum-scale=0.25, maximum-scale=10, user-scalable=yes';
        } else {
            meta.content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no';
        }
        head.prepend(meta);
    };
    applyViewport();

    //菜单命令
    GM_registerMenuCommand(`设置X轴倍率(当前 ${mx})`, ()=>{
        let v = parseFloat(prompt('X 轴滑动倍率:', mx));
        if(!isNaN(v)){ GM_setValue('multiplierX', mx = v); location.reload(); }
    });
    GM_registerMenuCommand(`设置Y轴倍率(当前 ${my})`, ()=>{
        let v = parseFloat(prompt('Y 轴滑动倍率:', my));
        if(!isNaN(v)){ GM_setValue('multiplierY', my = v); location.reload(); }
    });
    GM_registerMenuCommand(`设置更新间隔(当前 ${intervalSec}s)`, ()=>{
        let v = parseFloat(prompt('更新间隔(秒,0=仅松手时刷新):', intervalSec));
        if(!isNaN(v)){ GM_setValue('interval', intervalSec = v); location.reload(); }
    });
    GM_registerMenuCommand(`切换双指缩放+双击复位(当前 ${enableZoomAndReset?'开':'关'})`, ()=>{
        GM_setValue('enableZoomAndReset', enableZoomAndReset = !enableZoomAndReset);
        location.reload();
    });

    const touchMap = {}; // id -> { sx, sy, cx, cy, el, lastDx, lastDy }
    let timerId = null;
    function periodicUpdate(){
        for(let id in touchMap){
            let info = touchMap[id];
            if(info.cx==null||info.cy==null) continue;
            let totalDx = (info.sx - info.cx) * mx;
            let totalDy = (info.sy - info.cy) * my;
            let dX = totalDx - (info.lastDx||0);
            let dY = totalDy - (info.lastDy||0);
            if(dX) info.el.scrollLeft += dX;
            if(dY) info.el.scrollTop  += dY;
            info.lastDx = totalDx;
            info.lastDy = totalDy;
        }
    }
    function startTimer(){
        if(intervalSec>0 && !timerId){
            timerId = setInterval(periodicUpdate, intervalSec*1000);
        }
    }
    function stopTimer(){
        if(timerId){ clearInterval(timerId); timerId = null; }
    }

    document.addEventListener('touchstart', e=>{
        if(e.touches.length>1) return; // 多指交给缩放
        for(let t of e.changedTouches){
            let el = t.target;
            while(el && el!==document){
                let style = getComputedStyle(el);
                let canY = el.scrollHeight>el.clientHeight && /auto|scroll/.test(style.overflowY);
                let canX = el.scrollWidth>el.clientWidth   && /auto|scroll/.test(style.overflowX);
                if(canX||canY) break;
                el = el.parentNode;
            }
            if(!el||el===document){
                el = document.scrollingElement||document.documentElement;
            }
            touchMap[t.identifier] = {
                sx: t.clientX, sy: t.clientY,
                cx: t.clientX, cy: t.clientY,
                el,
                lastDx:0, lastDy:0
            };
        }
        startTimer();
    }, { passive:false });

    document.addEventListener('touchmove', e=>{
        if(e.touches.length>1) return; // 多指缩放
        let doPrevent = false;
        for(let t of e.changedTouches){
            let info = touchMap[t.identifier];
            if(!info) continue;
            info.cx = t.clientX;
            info.cy = t.clientY;
            // 允许顶端下拉刷新
            if(!(info.el === (document.scrollingElement||document.documentElement)
                 && info.el.scrollTop===0
                 && (t.clientY-info.sy)>0)){
                doPrevent = true;
            }
        }
        if(doPrevent) e.preventDefault();
    }, { passive:false });

    function finishTouch(e){
        if(e.touches.length>1) return;
        for(let t of e.changedTouches){
            let info = touchMap[t.identifier];
            if(!info) continue;
            if(intervalSec===0){
                let dx = (info.sx - t.clientX)*mx;
                let dy = (info.sy - t.clientY)*my;
                info.el.scrollLeft += dx;
                info.el.scrollTop  += dy;
            } else {
                info.cx = t.clientX;
                info.cy = t.clientY;
                periodicUpdate();
            }
            delete touchMap[t.identifier];
        }
        if(Object.keys(touchMap).length===0) stopTimer();
    }
    document.addEventListener('touchend', finishTouch, { passive:false });
    document.addEventListener('touchcancel', finishTouch, { passive:false });

    //双击复位
    let lastTap = 0;
    document.addEventListener('touchend', e=>{
        if(!enableZoomAndReset) return;
        if(e.touches.length===0 && e.changedTouches.length===1){
            let now = Date.now();
            if(now - lastTap < 300){
                // 复位 viewport initial-scale
                let vm = document.querySelector('meta[name=viewport]');
                if(vm){
                    let parts = vm.content.split(',');
                    for(let i=0;i<parts.length;i++){
                        if(parts[i].trim().startsWith('initial-scale')){
                            parts[i] = ' initial-scale=1';
                        }
                    }
                    vm.content = parts.join(',');
                }
            }
            lastTap = now;
        }
    }, { passive:true });

})();