您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Configurable smooth scroll with optional motion blur. Uses requestAnimationFrame (like V-Sync).
// ==UserScript== // @name Smooth Scroll // @description Configurable smooth scroll with optional motion blur. Uses requestAnimationFrame (like V-Sync). // @author DARK1E // @icon https://i.imgur.com/IAwk6NN.png // @include * // @version 3.3 // @namespace sttb-dxrk1e // @license MIT // @grant none // @run-at document-start // ==/UserScript== (function () { 'use strict'; const cfg = { smth: 0.85, stpMult: 1, accDelFct: 0.2, accMaxMult: 3, thrsh: 1, lnHt: 20, mBlur: false, mBlurInt: 0.3, dbg: false, }; const stMap = new WeakMap(); const DAMP_FCT = 1 - cfg.smth; const ACC_TMO = 150; const MAX_BLUR = 5; const BLUR_THRESH = 0.2; function _animStep(el) { const st = stMap.get(el); if (!st) return; const curScrTop = _getScrTop(el); const delta = st.tgtY - st.curY; if (Math.abs(delta) < cfg.thrsh && Math.abs(curScrTop - st.tgtY) < cfg.thrsh) { if (cfg.dbg) console.log("SS: Anim end", el); if (Math.abs(curScrTop - st.tgtY) > 0.1) { _setScrTop(el, Math.round(st.tgtY)); } _cancelAnim(el); return; } const step = delta * DAMP_FCT; st.curY += step; const scrAmt = Math.round(st.curY) - curScrTop; if (scrAmt !== 0) { const origBehav = _setBehav(el, 'auto'); _setScrTop(el, curScrTop + scrAmt); } if (cfg.mBlur) { const blurPx = Math.min(MAX_BLUR, Math.abs(step) * cfg.mBlurInt); if (blurPx > BLUR_THRESH) { _setFilter(el, `blur(${blurPx.toFixed(1)}px)`); } else { _setFilter(el, 'none'); } } st.animId = requestAnimationFrame(() => _animStep(el)); } function _startOrUpd(el, dY) { let st = stMap.get(el); const now = performance.now(); if (!st) { st = { tgtY: _getScrTop(el), curY: _getScrTop(el), animId: null, ts: 0, mult: 1, }; stMap.set(el, st); } const dt = now - st.ts; if (dt < ACC_TMO) { const accInc = Math.abs(dY) * cfg.accDelFct / cfg.lnHt; st.mult = Math.min(cfg.accMaxMult, st.mult + accInc); } else { st.mult = 1; } st.ts = now; const effDel = dY * st.mult * cfg.stpMult; st.tgtY += effDel; st.tgtY = _clampTgt(el, st.tgtY); if (cfg.dbg) { console.log(`SS: Upd Tgt`, el, `| dY: ${dY.toFixed(2)}`, `| mult: ${st.mult.toFixed(2)}`, `| effDel: ${effDel.toFixed(2)}`, `| tgtY: ${st.tgtY.toFixed(2)}`); } if (!st.animId) { st.curY = _getScrTop(el); if (cfg.dbg) console.log("SS: Start anim", el); st.animId = requestAnimationFrame(() => _animStep(el)); } } function _cancelAnim(el) { const st = stMap.get(el); if (st?.animId) { cancelAnimationFrame(st.animId); stMap.delete(el); if (cfg.dbg) console.log("SS: Anim cancelled", el); } if (cfg.mBlur) { _setFilter(el, 'none'); } } function _getScrTop(el) { return (el === window) ? (window.scrollY || document.documentElement.scrollTop) : /** @type {Element} */ (el).scrollTop; } function _setScrTop(el, val) { if (el === window) { document.documentElement.scrollTop = val; } else { /** @type {Element} */ (el).scrollTop = val; } } function _setBehav(el, behav) { const target = (el === window) ? document.documentElement : el; if (target instanceof Element) { const orig = target.style.scrollBehavior; target.style.scrollBehavior = behav; return orig; } return undefined; } function _setFilter(el, val) { const target = (el === window) ? document.documentElement : el; if (target instanceof HTMLElement) { try { target.style.filter = val; } catch (e) { if (cfg.dbg) console.warn("SS: Failed to set filter on", target, e); } } } function _clampTgt(el, tgtY) { let maxScr; if (el === window) { maxScr = document.documentElement.scrollHeight - window.innerHeight; } else { const htmlEl = /** @type {Element} */ (el); maxScr = htmlEl.scrollHeight - htmlEl.clientHeight; } return Math.max(0, Math.min(tgtY, maxScr)); } function _isScr(el) { if (!el || !(el instanceof Element) || el === document.documentElement || el === document.body) { return false; } try { const style = window.getComputedStyle(el); const ovf = style.overflowY; const isOvf = ovf === 'scroll' || ovf === 'auto'; const canScr = el.scrollHeight > el.clientHeight + 1; return isOvf && canScr; } catch (e) { if (cfg.dbg) console.warn("SS: Err check scroll", el, e); return false; } } function _getTgt(e) { const path = e.composedPath ? e.composedPath() : []; for (const el of path) { if (!(el instanceof Element)) continue; if (_isScr(el)) { const curScr = _getScrTop(el); const maxScr = el.scrollHeight - el.clientHeight; if ((e.deltaY < 0 && curScr > 0.1) || (e.deltaY > 0 && curScr < maxScr - 0.1)) { if (cfg.dbg) console.log("SS: Found el in path:", el); return el; } } if (el === document.body || el === document.documentElement) { break; } } const docEl = document.documentElement; const maxPgScr = docEl.scrollHeight - window.innerHeight; const curPgScr = _getScrTop(window); if ((e.deltaY < 0 && curPgScr > 0.1) || (e.deltaY > 0 && curPgScr < maxPgScr - 0.1)) { if (cfg.dbg) console.log("SS: Using win scroll"); return window; } if (cfg.dbg) console.log("SS: No scroll target found."); return null; } function _getPxDel(e, tgtEl) { let delta = e.deltaY; if (e.deltaMode === 1) { delta *= cfg.lnHt; } else if (e.deltaMode === 2) { const clHt = (tgtEl === window) ? window.innerHeight : /** @type {Element} */ (tgtEl).clientHeight; delta *= clHt * 0.9; } return delta; } function _hdlWheel(e) { if (e.deltaX !== 0 || e.ctrlKey || e.altKey ) { if (cfg.dbg) console.log("SS: Ignore event (X/mod)", e); return; } const tgtEl = _getTgt(e); if (!tgtEl) { if (cfg.dbg) console.log("SS: No target, native scroll"); return; } e.preventDefault(); const pxDel = _getPxDel(e, tgtEl); _startOrUpd(tgtEl, pxDel); } function _hdlClick(e) { const path = e.composedPath ? e.composedPath() : []; for (const el of path) { if (el instanceof Element || el === window) { _cancelAnim(el); } if (el === window) break; } _cancelAnim(window); } function _init() { if (window.top !== window.self && !window.location.href.match(/debug=true/)) { console.log("SS: Iframe detected, skip."); return; } if (window.SSEnhLoaded_NC) { // Changed flag slightly console.log("SS: Already loaded."); return; } document.documentElement.addEventListener('wheel', _hdlWheel, { passive: false, capture: true }); document.documentElement.addEventListener('mousedown', _hdlClick, { passive: true, capture: true }); document.documentElement.addEventListener('touchstart', _hdlClick, { passive: true, capture: true }); window.SSEnhLoaded_NC = true; console.log(`Enhanced Smooth Scroll (Short+FX, No Comments): Initialized (v3.3) | Motion Blur: ${cfg.mBlur}`); if (cfg.dbg) console.log("SS: Debug mode enabled."); } _init(); })();