Greasy Fork

来自缓存

Greasy Fork is available in English.

墨水屏电纸书划动优化

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         墨水屏电纸书划动优化
// @namespace    cc.cxuan.books
// @version      1.27
// @description  消除划动动画 可设置划动倍率和更新间隔 可设置双指放大双击复原
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// @noframes
// @license      MIT
// @author       cxuan.cc
// ==/UserScript==
(function(){
    // ---- 强制取消原生缩放限制 ----
    const vcontent = 'width=device-width, initial-scale=1, maximum-scale=10, minimum-scale=0.1, user-scalable=yes';
    let meta = document.querySelector('meta[name=viewport]');
    if(meta) meta.setAttribute('content', vcontent);
    else {
        meta = document.createElement('meta');
        meta.name = 'viewport';
        meta.content = vcontent;
        document.head.appendChild(meta);
    }

    // --- 设置项 ---
    let mx = GM_getValue('multiplierX', 5);
    let my = GM_getValue('multiplierY', 2);
    let intervalSec = GM_getValue('interval', 0);
    let enableZoom = GM_getValue('enableZoom', true);
    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(`切换两指缩放+双击复位(当前 ${enableZoom?'开':'关'})`, ()=>{
        GM_setValue('enableZoom', enableZoom=!enableZoom);
        location.reload();
    });

    const initialZoom = parseFloat(document.body.style.zoom) || 1;
    const touchMap = {};
    let timerId = null;
    let pinchState = null;  // { initialDist, initialScale }

    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(enableZoom && e.touches.length===2){
            let [a,b] = e.touches;
            let d = Math.hypot(a.clientX-b.clientX, a.clientY-b.clientY);
            pinchState = { initialDist: d, initialScale: parseFloat(document.body.style.zoom)||1 };
            e.preventDefault();
            return;
        }
        if(e.touches.length>1) return;
        for(const t of e.changedTouches){
            let el = t.target;
            while(el && el!==document){
                let s = getComputedStyle(el);
                if((el.scrollHeight>el.clientHeight && /auto|scroll/.test(s.overflowY))
                   || (el.scrollWidth>el.clientWidth && /auto|scroll/.test(s.overflowX))) 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(enableZoom && e.touches.length===2 && pinchState){
            let [a,b] = e.touches;
            let d = Math.hypot(a.clientX-b.clientX, a.clientY-b.clientY);
            let scale = pinchState.initialScale*(d/pinchState.initialDist);
            document.body.style.zoom = scale;
            e.preventDefault();
            return;
        }
        if(e.touches.length>1) return;
        let doPrevent = false;
        for(const 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(pinchState && e.touches.length<2){
            pinchState = null;
            // 继续处理后续单指结束
        }
        if(e.touches.length>1) return;
        for(const 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(!enableZoom) return;
        if(e.touches.length===0 && e.changedTouches.length===1){
            let now = Date.now();
            if(now - lastTap < 300){
                // 恢复脚本启动时的 zoom
                let sx = document.scrollingElement.scrollLeft||0;
                let sy = document.scrollingElement.scrollTop||0;
                document.body.style.zoom = initialZoom;
                document.scrollingElement.scrollLeft = sx;
                document.scrollingElement.scrollTop = sy;
            }
            lastTap = now;
        }
    }, { passive:true });

})();