// ==UserScript==
// @name 墨水屏电纸书划动优化+双指缩放
// @namespace cc.cxuan.books
// @version 1.25
// @description 消除划动动画 可设置划动倍率和更新间隔 可设置双指放大双击复原
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-end
// @noframes
// @license MIT
// @author cxuan.cc
// ==/UserScript==
(function(){
// --- 强制解除网页原缩放限制 ---
let mv = document.querySelector('meta[name=viewport]');
if(mv){
mv.setAttribute('content','width=device-width, initial-scale=1, minimum-scale=0.1, maximum-scale=10, user-scalable=yes');
} else {
let m = document.createElement('meta');
m.name='viewport';
m.content='width=device-width, initial-scale=1, minimum-scale=0.1, maximum-scale=10, user-scalable=yes';
document.head.appendChild(m);
}
// --- 设置项 ---
let mx = GM_getValue('multiplierX', 5);
let my = GM_getValue('multiplierY', 2);
let intervalSec = GM_getValue('interval', 0);
let enableDoubleReset = GM_getValue('enableDoubleReset', 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(`切换双击复位(当前 ${enableDoubleReset ? '开' : '关'})`, ()=>{
enableDoubleReset = !enableDoubleReset;
GM_setValue('enableDoubleReset', enableDoubleReset);
location.reload();
});
// --- 捏合缩放相关 ---
let initialZoom = parseFloat(document.body.style.zoom) || 1;
let currentZoom = initialZoom;
let pinchStartDist = 0;
let pinchStartZoom = initialZoom;
function getDist(t1, t2){
let dx = t1.clientX - t2.clientX, dy = t1.clientY - t2.clientY;
return Math.hypot(dx, dy);
}
document.addEventListener('touchstart', e=>{
if(e.touches.length===2){
// 开始捏合
pinchStartDist = getDist(e.touches[0], e.touches[1]);
pinchStartZoom = currentZoom;
}
},{passive:false});
document.addEventListener('touchmove', e=>{
if(e.touches.length===2){
// 处理捏合
let d = getDist(e.touches[0], e.touches[1]);
let scale = pinchStartZoom * (d / pinchStartDist);
currentZoom = scale;
document.body.style.zoom = scale;
e.preventDefault();
return;
}
},{passive:false});
// --- 触摸滑动优化 ---
const touchMap = {};
let timerId = null;
function periodicUpdate(){
for(let id in touchMap){
let info = touchMap[id];
if(info.cx==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 st = getComputedStyle(el);
if((el.scrollHeight>el.clientHeight && /auto|scroll/.test(st.overflowY))
||(el.scrollWidth>el.clientWidth && /auto|scroll/.test(st.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(e.touches.length>1) return;
let prevent=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)){
prevent = true;
}
}
if(prevent) 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(!enableDoubleReset) return;
if(e.touches.length===0 && e.changedTouches.length===1){
let now = Date.now();
if(now - lastTap < 300){
// 恢复到捏合前初始比例
document.body.style.zoom = initialZoom;
currentZoom = initialZoom;
}
lastTap = now;
}
},{passive:true});
})();