您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
消除划动动画 可设置划动倍率和更新间隔 可设置双击放大复原
// ==UserScript== // @name 墨水屏电纸书划动 // @namespace cc.cxuan.books // @version 1.30 // @description 消除划动动画 可设置划动倍率和更新间隔 可设置双击放大复原 // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @run-at document-end // @noframes // @license MIT // @author cxuan.cc // ==/UserScript== (function(){ // 设置项 let my = GM_getValue('multiplierY', 2.5); let mx = 2; let intervalSec = GM_getValue('interval', 0); let doubleClickZoom = GM_getValue('doubleClickZoom', 2); let enableMx = GM_getValue('enableMx', false); 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(`设置双击放大倍率(当前 ${doubleClickZoom})`, ()=>{ let v = parseFloat(prompt('双击放大倍率(0=无双击放大):', doubleClickZoom)); if (!isNaN(v)) { GM_setValue('doubleClickZoom', doubleClickZoom = v); location.reload(); } }); GM_registerMenuCommand(`切换x轴移动优化开关 固定${mx}倍(当前 ${enableMx ? '开' : '关'})`, ()=>{ enableMx = !enableMx; GM_setValue('enableMx', enableMx); location.reload(); }); //开启或禁止双指缩放 let meta = document.querySelector('meta[name=viewport]'); const vp = 'width=device-width, initial-scale=1, minimum-scale=0.25, maximum-scale=10, user-scalable=yes'; const applyViewport = ()=>{ let head = document.head || document.getElementsByTagName('head')[0] || document.documentElement; // 添加新的 if(doubleClickZoom > 0){ if (meta) { meta.setAttribute('content', vp); } else { meta = document.createElement('meta'); meta.name = 'viewport'; meta.content = vp; document.head.appendChild(meta); } }else{ if (meta) { meta.setAttribute('content', vp); } } head.prepend(meta); }; applyViewport(); const initialZoom = parseFloat(document.body.style.zoom) || 1; 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 canScrollY = el.scrollHeight > el.clientHeight && /auto|scroll/.test(style.overflowY); const canScrollX = 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(!enableMx){ if (Math.abs(info.cx - info.sx) > Math.abs(info.cy - info.sy) * 2){ doPrevent = false; } } } 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; let isDoubleClickZoomBig = false; document.addEventListener('touchend', e=>{ if (doubleClickZoom == 0) return; // 仅单指 if (e.touches.length === 0 && e.changedTouches.length === 1) { let now = Date.now(); if (now - lastTap < 300) { // 修改或插入 viewport let meta = document.querySelector('meta[name=viewport]'); isDoubleClickZoomBig = !isDoubleClickZoomBig; const vp = `width=device-width, initial-scale=${isDoubleClickZoomBig ? doubleClickZoom : 1}, maximum-scale=10`; if (meta) { meta.setAttribute('content', vp); } else { meta = document.createElement('meta'); meta.name = 'viewport'; meta.content = vp; document.head.appendChild(meta); } } lastTap = now; } }, { passive:true }); })();