Greasy Fork

Greasy Fork is available in English.

墨水屏电纸书划动优化

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

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

// ==UserScript==
// @name         墨水屏电纸书划动优化
// @namespace    cc.cxuan.books
// @version      1.28
// @description  消除划动动画 可设置划动倍率和更新间隔 可设置双指放大双击复原
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// @noframes
// @license      MIT
// @author       cxuan.cc
// ==/UserScript==
(function(){
    'use strict';
    // 1. 强制移除meta viewport缩放限制,并读取原initial-scale
    let meta = document.querySelector('meta[name=viewport]'), initialScale = 1;
    if(meta){
        let c = meta.content, m = c.match(/initial-scale=([\d.]+)/);
        if(m) initialScale = parseFloat(m[1]);
        let nc = c.replace(/maximum-scale=[\d.]+/, '')
                  .replace(/minimum-scale=[\d.]+/, '')
                  .replace(/user-scalable=\w+/, '').trim();
        meta.content = nc + (nc?',' : '') + 'minimum-scale=0.1,maximum-scale=10,user-scalable=yes';
    }
    document.body.style.zoom = initialScale;  // 应用初始缩放

    // --- 设置 ---
    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?'开':'关'})`, ()=>{
        enableZoom = !enableZoom;
        GM_setValue('enableZoom',enableZoom);
        location.reload();
    });

    const touchMap = {}, timer = { id:0 };
    function periodicUpdate(){
        let zoom = parseFloat(document.body.style.zoom)||1;
        for(let id in touchMap){
            let info = touchMap[id];
            if(info.cx==null) continue;
            let dxTotal = (info.sx - info.cx) * mx / zoom;
            let dyTotal = (info.sy - info.cy) * my / zoom;
            let dX = dxTotal - (info.lastDx||0);
            let dY = dyTotal - (info.lastDy||0);
            if(dX) info.el.scrollLeft += dX;
            if(dY) info.el.scrollTop += dY;
            info.lastDx = dxTotal;
            info.lastDy = dyTotal;
        }
    }
    function startTimer(){
        if(intervalSec>0 && !timer.id) timer.id = setInterval(periodicUpdate, intervalSec*1000);
    }
    function stopTimer(){
        if(timer.id){ clearInterval(timer.id); timer.id=0; }
    }

    // 捏合缩放记录
    const pinch = { startDist:0, startZoom:initialScale };

    document.addEventListener('touchstart', e=>{
        if(enableZoom && e.touches.length===2){
            let [t0,t1] = e.touches;
            pinch.startDist = Math.hypot(t0.clientX-t1.clientX,t0.clientY-t1.clientY);
            pinch.startZoom = parseFloat(document.body.style.zoom)||initialScale;
            e.preventDefault();
            return;
        }
        if(e.touches.length>1) return;
        for(let 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){
            let [t0,t1] = e.touches;
            let dist = Math.hypot(t0.clientX-t1.clientX,t0.clientY-t1.clientY);
            document.body.style.zoom = pinch.startZoom * (dist / pinch.startDist);
            return;
        }
        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;
            let docEl = document.scrollingElement||document.documentElement;
            if(!(info.el===docEl && docEl.scrollTop===0 && (t.clientY-info.sy)>0)){
                doPrevent=true;
            }
        }
        if(doPrevent) e.preventDefault();
    },{passive:false});

    function finishTouch(e){
        if(enableZoom && e.touches.length>=1) return;
        if(!enableZoom && e.touches.length>1) return;
        for(let t of e.changedTouches){
            let info = touchMap[t.identifier];
            if(!info) continue;
            let zoom = parseFloat(document.body.style.zoom)||1;
            if(intervalSec===0){
                let dx = (info.sx - t.clientX)*mx/zoom;
                let dy = (info.sy - t.clientY)*my/zoom;
                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) stopTimer();
    }
    document.addEventListener('touchend', finishTouch,{passive:false});
    document.addEventListener('touchcancel', finishTouch,{passive:false});

    // 双击复位到 initialScale
    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){
                document.body.style.zoom = initialScale;
            }
            lastTap = now;
        }
    },{passive:true});

})();