Greasy Fork is available in English.
消除划动动画 可设置划动倍率和更新间隔 可设置双指放大双击复原
当前为
// ==UserScript==
// @name 墨水屏电纸书划动优化
// @namespace cc.cxuan.books
// @version 1.22
// @description 消除划动动画 可设置划动倍率和更新间隔 可设置双指放大双击复原
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-end
// @noframes
// @license MIT
// @author cxuan.cc
// ==/UserScript==
(function(){
// --- 设置项 ---
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();
});
// 记录原始 viewport 内容,用于双击复位
let vpMeta = document.querySelector('meta[name=viewport]');
if (!vpMeta) {
vpMeta = document.createElement('meta');
vpMeta.name = 'viewport';
document.head.appendChild(vpMeta);
}
const origVpContent = vpMeta.getAttribute('content') || '';
const touchMap = {}; // id -> { sx, sy, cx, cy, el, lastDx, lastDy }
let timerId = null;
function periodicUpdate() {
for (let id in touchMap) {
const 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 (const t of e.changedTouches) {
let el = t.target;
while(el && el!==document){
const style = getComputedStyle(el);
const isDocEl = el===document.scrollingElement||el===document.documentElement;
// 强制把根文档当做可滚动容器
const canScrollY = (isDocEl && el.scrollHeight>el.clientHeight)
|| (el.scrollHeight>el.clientHeight && /auto|scroll/.test(style.overflowY));
const canScrollX = (isDocEl && el.scrollWidth>el.clientWidth)
|| (el.scrollWidth>el.clientWidth && /auto|scroll/.test(style.overflowX));
if (canScrollY || canScrollX) 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 (const t of e.changedTouches) {
const 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 (const t of e.changedTouches) {
const 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 });
// 双击复位——恢复浏览器级 viewport
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) {
// 恢复原始 meta viewport
vpMeta.setAttribute('content', origVpContent);
}
lastTap = now;
}
}, { passive:true });
})();