您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Smooth automatic scrolling with HUD controls. Toggle with 'S'. Adjust speed with '[' and ']'. Change step with '+/-'. Reset with 'R'. Hide HUD with 'H'. Block sites directly from HUD.
当前为
// ==UserScript== // @name Enhanced Smooth AutoScroll // @namespace https://gf.qytechs.cn/users/1513610 // @version 2.2 // @description Smooth automatic scrolling with HUD controls. Toggle with 'S'. Adjust speed with '[' and ']'. Change step with '+/-'. Reset with 'R'. Hide HUD with 'H'. Block sites directly from HUD. // @author NAABO // @license MIT // @match *://*/* // @grant none // @run-at document-idle // ==/UserScript== /* 📌 Features: - Press 'S' to start/pause smooth scrolling. - '[' / ']' decrease/increase scroll speed. - '+' / '-' adjust speed step size. - 'R' resets to default speed. - 'H' shows/hides the HUD. - HUD includes buttons for controls + 🚫 blocklist per site. - Respects "prefers-reduced-motion". */ (function () { 'use strict'; /************* Configuration *************/ const CONFIG = { STORAGE_KEY: 'enhanced_autoscroll_config', BLOCKLIST_KEY: 'enhanced_autoscroll_blocklist', DEFAULT_SPEED: 100, DEFAULT_SPEED_STEP: 10, MIN_SPEED_STEP: 1, MAX_SPEED_STEP: 50, HUD_POSITIONS: ['bottom-right', 'bottom-left', 'top-right', 'top-left'], DEBOUNCE_DELAY: 100, FLASH_DURATION: 1500, }; /************* State *************/ let state = { enabled: false, speed: CONFIG.DEFAULT_SPEED, speedStep: CONFIG.DEFAULT_SPEED_STEP, lastFrameTime: null, rafId: null, hud: null, hudVisible: true, hudPosition: 'bottom-right', terminated: false, flashTimeout: null, keyDebounceTimeout: null, respectsReducedMotion: true, }; /************* Config Persistence *************/ function loadConfig() { try { const saved = localStorage.getItem(CONFIG.STORAGE_KEY); if (saved) { const config = JSON.parse(saved); state.speed = Number(config.speed) || CONFIG.DEFAULT_SPEED; state.speedStep = Number(config.speedStep) || CONFIG.DEFAULT_SPEED_STEP; state.hudPosition = config.hudPosition || 'bottom-right'; state.respectsReducedMotion = Boolean(config.respectsReducedMotion); } } catch (error) { console.warn('Enhanced AutoScroll: Failed to load config', error); } } function saveConfig() { try { const config = { speed: state.speed, speedStep: state.speedStep, hudPosition: state.hudPosition, respectsReducedMotion: state.respectsReducedMotion, }; localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify(config)); } catch (error) { console.warn('Enhanced AutoScroll: Failed to save config', error); } } function loadBlocklist() { try { const saved = localStorage.getItem(CONFIG.BLOCKLIST_KEY); return saved ? JSON.parse(saved) : []; } catch { return []; } } function saveBlocklist(list) { try { localStorage.setItem(CONFIG.BLOCKLIST_KEY, JSON.stringify(list)); } catch {} } /************* Helpers *************/ function isTyping(event) { const tgt = event.target; if (!tgt) return false; const tag = (tgt.tagName || '').toLowerCase(); return tag === 'input' || tag === 'textarea' || tgt.isContentEditable; } function prefersReducedMotion() { return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; } function debounce(func, delay) { return function (...args) { if (state.keyDebounceTimeout) clearTimeout(state.keyDebounceTimeout); state.keyDebounceTimeout = setTimeout(() => func.apply(this, args), delay); }; } function safeRAF(callback) { try { return requestAnimationFrame(callback); } catch { return setTimeout(callback, 16); } } function safeCancelRAF(id) { try { cancelAnimationFrame(id); } catch { clearTimeout(id); } } /************* Scrolling Engine *************/ function step(now) { if (state.terminated) return; if (state.respectsReducedMotion && prefersReducedMotion()) { if (state.enabled) { toggleEnabled(false); flashHUD('Paused: Reduced motion preferred'); } return; } if (!state.lastFrameTime) state.lastFrameTime = now; const dt = Math.min((now - state.lastFrameTime) / 1000, 0.1); state.lastFrameTime = now; if (state.enabled) { const delta = state.speed * dt; const maxScroll = Math.max(0, document.documentElement.scrollHeight - window.innerHeight); const currentY = window.scrollY || window.pageYOffset || 0; if (state.speed > 0 && currentY >= Math.floor(maxScroll)) { toggleEnabled(false); flashHUD('End of page reached'); } else if (state.speed < 0 && currentY <= 0) { toggleEnabled(false); flashHUD('Top of page reached'); } else { const newY = Math.max(0, Math.min(maxScroll, currentY + delta)); window.scrollTo({ top: newY, behavior: 'instant' }); } } if (!state.terminated) state.rafId = safeRAF(step); } function startLoop() { if (!state.rafId) { state.lastFrameTime = null; state.rafId = safeRAF(step); } } function stopLoop() { if (state.rafId) { safeCancelRAF(state.rafId); state.rafId = null; } state.lastFrameTime = null; } /************* Controls *************/ function toggleEnabled(force) { state.enabled = typeof force === 'boolean' ? force : !state.enabled; updateHUD(); if (state.enabled) { startLoop(); flashHUD(`Scrolling ${state.speed >= 0 ? 'down' : 'up'} at ${Math.abs(state.speed)} px/s`); } else { stopLoop(); flashHUD('Scrolling paused'); } saveConfig(); } function adjustSpeed(delta) { const oldSpeed = state.speed; state.speed += delta; const direction = state.speed >= 0 ? '↓' : '↑'; updateHUD(); flashHUD(`Speed: ${Math.abs(state.speed)} px/s ${direction}`); saveConfig(); if (state.enabled && Math.sign(oldSpeed) !== Math.sign(state.speed)) { flashHUD(`Direction changed! Speed: ${Math.abs(state.speed)} px/s ${direction}`); } } function resetSpeed() { state.speed = CONFIG.DEFAULT_SPEED; updateHUD(); flashHUD(`Speed reset to ${CONFIG.DEFAULT_SPEED} px/s`); saveConfig(); } /************* HUD *************/ function getHUDPositionStyles() { return { 'bottom-right': 'right:12px; bottom:12px;', 'bottom-left': 'left:12px; bottom:12px;', 'top-right': 'right:12px; top:12px;', 'top-left': 'left:12px; top:12px;', }[state.hudPosition] || 'right:12px; bottom:12px;'; } function createHUD() { if (state.hud) state.hud.remove(); state.hud = document.createElement('div'); state.hud.id = 'enhanced-autoscroll-hud'; state.hud.style.cssText = ` position:fixed; ${getHUDPositionStyles()} z-index:999999; padding:10px 14px; background:rgba(0,0,0,0.75); color:#fff; font-family:system-ui, sans-serif; font-size:13px; border-radius:10px; box-shadow:0 8px 24px rgba(0,0,0,0.6); backdrop-filter:blur(8px); max-width:340px; pointer-events:auto; opacity:0.95; border:1px solid rgba(255,255,255,0.1); `; state.hud.innerHTML = ` <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;"> <div id="hud-status" style="font-weight:600; font-size:14px;">PAUSED</div> <button id="hud-close" style=" background:none; border:none; color:#fff; font-size:16px; cursor:pointer; padding:2px 6px; opacity:0.7; " title="Close Enhanced AutoScroll">×</button> </div> <div id="hud-speed" style="margin-bottom:8px; font-size:12px; opacity:0.9;"></div> <div id="hud-config" style="margin-bottom:8px; font-size:11px; opacity:0.8;"></div> <div id="hud-buttons" style="margin-bottom:8px; display:flex; gap:4px; flex-wrap:wrap;"> <button class="hud-btn" data-action="toggle">S</button> <button class="hud-btn" data-action="speed-down">[</button> <button class="hud-btn" data-action="speed-up">]</button> <button class="hud-btn" data-action="reset">R</button> <button class="hud-btn" data-action="hide-hud">H</button> <button class="hud-btn" data-action="step-up">+</button> <button class="hud-btn" data-action="step-down">-</button> <button class="hud-btn" data-action="block-site" title="Block this site">🚫</button> </div> <div style="font-size:9px; opacity:0.7; line-height:1.3;"> Use keyboard shortcuts or click buttons </div> `; document.body.appendChild(state.hud); state.hud.querySelector('#hud-close').addEventListener('click', shutdownScript); setupHUDButtons(); } function setupHUDButtons() { state.hud.querySelectorAll('.hud-btn').forEach(button => { const action = button.dataset.action; button.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); switch (action) { case 'toggle': toggleEnabled(); break; case 'speed-down': adjustSpeed(-state.speedStep); break; case 'speed-up': adjustSpeed(state.speedStep); break; case 'reset': resetSpeed(); break; case 'hide-hud': state.hudVisible = false; updateHUD(); flashHUD('HUD hidden - Press H to show', 3000); break; case 'step-up': if (state.speedStep < CONFIG.MAX_SPEED_STEP) { state.speedStep++; saveConfig(); updateHUD(); flashHUD(`Speed step: ${state.speedStep}`); } break; case 'step-down': if (state.speedStep > CONFIG.MIN_SPEED_STEP) { state.speedStep--; saveConfig(); updateHUD(); flashHUD(`Speed step: ${state.speedStep}`); } break; case 'block-site': const domain = window.location.hostname; if (confirm(`Block Enhanced AutoScroll on ${domain}?`)) { const list = loadBlocklist(); if (!list.includes(domain)) { list.push(domain); saveBlocklist(list); } flashHUD(`Blocked on ${domain}`); shutdownScript(); } break; } }); }); } function updateHUD() { if (!state.hud) createHUD(); if (!state.hudVisible) { state.hud.style.display = 'none'; return; } state.hud.style.display = 'block'; const statusEl = state.hud.querySelector('#hud-status'); const speedEl = state.hud.querySelector('#hud-speed'); const configEl = state.hud.querySelector('#hud-config'); if (state.enabled) { statusEl.textContent = `SCROLLING ${state.speed >= 0 ? '↓' : '↑'}`; statusEl.style.color = '#4ade80'; } else { statusEl.textContent = 'PAUSED'; statusEl.style.color = '#ef4444'; } speedEl.textContent = `Speed: ${Math.abs(state.speed)} px/s (Step: ${state.speedStep})`; configEl.textContent = (state.respectsReducedMotion && prefersReducedMotion()) ? 'Reduced motion' : ''; } function flashHUD(text, duration = CONFIG.FLASH_DURATION) { if (!state.hud || !state.hudVisible) return; const statusEl = state.hud.querySelector('#hud-status'); statusEl.textContent = text; statusEl.style.color = '#60a5fa'; if (state.flashTimeout) clearTimeout(state.flashTimeout); state.flashTimeout = setTimeout(() => updateHUD(), duration); } /************* Shutdown *************/ function shutdownScript() { if (state.terminated) return; state.terminated = true; stopLoop(); state.enabled = false; clearTimeout(state.flashTimeout); clearTimeout(state.keyDebounceTimeout); document.removeEventListener('keydown', onKeyDown); window.removeEventListener('beforeunload', shutdownScript); if (state.hud) state.hud.remove(); state.hud = null; console.log('Enhanced AutoScroll: Script terminated'); } /************* Key Handling *************/ const onKeyDown = debounce(function (e) { if (isTyping(e) || state.terminated) return; switch (e.key.toLowerCase()) { case 's': e.preventDefault(); toggleEnabled(); break; case '[': e.preventDefault(); adjustSpeed(-state.speedStep); break; case ']': e.preventDefault(); adjustSpeed(state.speedStep); break; case 'h': e.preventDefault(); state.hudVisible = !state.hudVisible; updateHUD(); flashHUD(`HUD ${state.hudVisible ? 'shown' : 'hidden'}`); break; case 'r': e.preventDefault(); resetSpeed(); break; case '+': case '=': e.preventDefault(); if (state.speedStep < CONFIG.MAX_SPEED_STEP) { state.speedStep++; saveConfig(); updateHUD(); flashHUD(`Speed step: ${state.speedStep}`); } break; case '-': case '_': e.preventDefault(); if (state.speedStep > CONFIG.MIN_SPEED_STEP) { state.speedStep--; saveConfig(); updateHUD(); flashHUD(`Speed step: ${state.speedStep}`); } break; } }, CONFIG.DEBOUNCE_DELAY); /************* Init *************/ function init() { const blocklist = loadBlocklist(); if (blocklist.includes(window.location.hostname)) { console.log(`Enhanced AutoScroll: Disabled on ${window.location.hostname}`); return; } loadConfig(); createHUD(); updateHUD(); document.addEventListener('keydown', onKeyDown, { passive: false }); window.addEventListener('beforeunload', shutdownScript, { passive: true }); flashHUD('Enhanced AutoScroll ready! Press S to start', 2000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址