墨水屏电纸书划动优化

消除划动动画 可设置划动倍率和更新间隔 支持手势缩放和双击复位(整体布局缩放)

当前为 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
// ==/UserScript==
(function(){
    // --- 设置项 ---
    let mx = GM_getValue('multiplierX', 5),
        my = GM_getValue('multiplierY', 2),
        intervalSec = GM_getValue('interval', 0),
        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 defaultScale = 1;
    let currentScale = defaultScale, pinch = null;
    function applyScale(s){
      const html = document.documentElement;
      html.style.transformOrigin = '0 0';
      html.style.transform = 'scale('+s+')';
      html.style.width = (100/s)+'vw';
    }

    // --- 滑动逻辑 ---
    const touchMap = {};
    let timerId = null;
    function periodicUpdate(){
      for(let id in touchMap){
        let info = touchMap[id];
        if(info.cx==null||info.cy==null) continue;
        let dxTot = (info.sx - info.cx)*mx,
            dyTot = (info.sy - info.cy)*my,
            dX = dxTot - (info.lastDx||0),
            dY = dyTot - (info.lastDy||0);
        if(dX) info.el.scrollLeft += dX;
        if(dY) info.el.scrollTop  += dY;
        info.lastDx = dxTot;
        info.lastDy = dyTot;
      }
    }
    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 [t1,t2] = e.touches;
        pinch = {
          initialDist: Math.hypot(t2.clientX-t1.clientX, t2.clientY-t1.clientY),
          initialScale: currentScale
        };
        e.preventDefault();
        return;
      }
      if(e.touches.length>1) return;
      for(let t of e.changedTouches){
        let el = t.target;
        while(el && el!==document){
          let st = getComputedStyle(el),
              canY = el.scrollHeight>el.clientHeight && /auto|scroll/.test(st.overflowY),
              canX = el.scrollWidth>el.clientWidth  && /auto|scroll/.test(st.overflowX);
          if(canY||canX) 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=>{
      // pinch 缩放
      if(pinch){
        let [t1,t2] = e.touches;
        let dist = Math.hypot(t2.clientX-t1.clientX, t2.clientY-t1.clientY);
        currentScale = +(pinch.initialScale * dist/pinch.initialDist).toFixed(2);
        applyScale(currentScale);
        e.preventDefault();
        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;
        // 保留下拉刷新
        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){
      // pinch 结束
      if(pinch && e.touches.length<2){
        pinch = null;
        return;
      }
      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,
              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) 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){
          let de = document.scrollingElement||document.documentElement,
              lx = de.scrollLeft, ly = de.scrollTop;
          currentScale = defaultScale;
          applyScale(currentScale);
          de.scrollLeft = lx;
          de.scrollTop  = ly;
        }
        lastTap = now;
      }
    }, { passive:false });

    // 页面载入时应用默认缩放
    if(enableZoom) applyScale(defaultScale);
})();