// ==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 // @downloadURL none // ==/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