Enhanced Smooth AutoScroll

Press 's' to toggle smooth auto-scroll. '[', ']' adjust speed. 'h' hides HUD. 'r' reset speed. '+/-' adjust step. 🚫 Block sites via HUD.

当前为 2025-09-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         Enhanced Smooth AutoScroll
// @version      2.1
// @description  Press 's' to toggle smooth auto-scroll. '[', ']' adjust speed. 'h' hides HUD. 'r' reset speed. '+/-' adjust step. 🚫 Block sites via HUD.
// @author       NAABO
// @match        *://*/*
// @grant        none
// @run-at       document-idle
// @namespace https://gf.qytechs.cn/users/1513610
// ==/UserScript==

(function () {
  'use strict';

  /************* Configuration & Constants *************/
  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,
    EASING_FACTOR: 0.2,
  };

  /************* State Management *************/
  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 Management *************/
  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 (error) {
      console.warn('Enhanced AutoScroll: Failed to load blocklist', error);
      return [];
    }
  }

  function saveBlocklist(list) {
    try {
      localStorage.setItem(CONFIG.BLOCKLIST_KEY, JSON.stringify(list));
    } catch (error) {
      console.warn('Enhanced AutoScroll: Failed to save blocklist', error);
    }
  }

  /************* Utility Functions *************/
  function isTyping(event) {
    try {
      const tgt = event.target;
      if (!tgt) return false;
      const tag = (tgt.tagName || '').toLowerCase();
      if (tag === 'input' || tag === 'textarea') return true;
      if (tgt.isContentEditable) return true;
      return false;
    } catch {
      return false;
    }
  }

  function prefersReducedMotion() {
    try {
      return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    } catch {
      return false;
    }
  }

  function debounce(func, delay) {
    return function (...args) {
      if (state.keyDebounceTimeout) clearTimeout(state.keyDebounceTimeout);
      state.keyDebounceTimeout = setTimeout(() => func.apply(this, args), delay);
    };
  }

  function safeRequestAnimationFrame(callback) {
    try {
      return requestAnimationFrame(callback);
    } catch {
      return setTimeout(callback, 16);
    }
  }

  function safeCancelAnimationFrame(id) {
    try {
      cancelAnimationFrame(id);
    } catch {
      clearTimeout(id);
    }
  }

  /************* Scrolling Engine *************/
  function step(now) {
    try {
      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 = safeRequestAnimationFrame(step);
    } catch (error) {
      console.error('Enhanced AutoScroll: Error in animation step', error);
      stopLoop();
    }
  }

  function startLoop() {
    if (!state.rafId) {
      state.lastFrameTime = null;
      state.rafId = safeRequestAnimationFrame(step);
    }
  }

  function stopLoop() {
    if (state.rafId) {
      safeCancelAnimationFrame(state.rafId);
      state.rafId = null;
    }
    state.lastFrameTime = null;
  }

  /************* Controls *************/
  function toggleEnabled(forceState) {
    if (typeof forceState === 'boolean') state.enabled = forceState;
    else state.enabled = !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 ? '↓' : '↑';
    const speedText = `Speed: ${Math.abs(state.speed)} px/s ${direction}`;
    updateHUD();
    flashHUD(speedText);
    saveConfig();
    if (state.enabled && Math.sign(oldSpeed) !== Math.sign(state.speed)) {
      flashHUD(`Direction changed! ${speedText}`);
    }
  }

  function resetSpeed() {
    state.speed = CONFIG.DEFAULT_SPEED;
    updateHUD();
    flashHUD(`Speed reset to ${CONFIG.DEFAULT_SPEED} px/s`);
    saveConfig();
  }

  /************* HUD *************/
  function getHUDPositionStyles() {
    const positions = {
      'bottom-right': 'right:12px; bottom:12px;',
      'bottom-left': 'left:12px; bottom:12px;',
      'top-right': 'right:12px; top:12px;',
      'top-left': 'left:12px; top:12px;',
    };
    return positions[state.hudPosition] || positions['bottom-right'];
  }

  function createHUD() {
    if (state.hud) state.hud.remove();
    state.hud = document.createElement('div');
    state.hud.setAttribute('id', 'enhanced-autoscroll-hud');
    const positionStyles = getHUDPositionStyles();

    state.hud.style.cssText = `
      position:fixed; ${positionStyles} z-index:999999;
      padding:10px 14px 12px 14px; background:rgba(0,0,0,0.75); color:#fff;
      font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial;
      font-size:13px; border-radius:10px; box-shadow:0 8px 24px rgba(0,0,0,0.6);
      backdrop-filter:blur(8px); max-width:340px; line-height:1.3;
      pointer-events:auto; opacity:0.95; transition:all 0.2s ease;
      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; line-height:1; opacity:0.7;
          border-radius:4px; transition:opacity 0.2s ease;
        " title="Close Enhanced AutoScroll">&times;</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;">
        Click buttons above or use keyboard shortcuts
      </div>
    `;
    document.body.appendChild(state.hud);

    state.hud.querySelector('#hud-close').addEventListener('click', shutdownScript);
    setupHUDButtons();
  }

  function setupHUDButtons() {
    const buttons = state.hud.querySelectorAll('.hud-btn');
    buttons.forEach(button => {
      const action = button.getAttribute('data-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) {
      const direction = state.speed >= 0 ? '↓' : '↑';
      statusEl.textContent = `SCROLLING ${direction}`;
      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');
    const originalText = statusEl.textContent;
    const originalColor = statusEl.style.color;
    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;
    if (state.flashTimeout) clearTimeout(state.flashTimeout);
    if (state.keyDebounceTimeout) 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;
    const key = e.key.toLowerCase();
    switch (key) {
      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);

  /************* Initialization *************/
  function init() {
    const blocklist = loadBlocklist();
    const domain = window.location.hostname;
    if (blocklist.includes(domain)) {
      console.log(`Enhanced AutoScroll: Disabled on blocked site ${domain}`);
      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或关注我们的公众号极客氢云获取最新地址