Enhanced Smooth AutoScroll

Press 's' to toggle smooth auto-scroll. '[', ']' adjust speed (negative = scroll up). 'h' hides/shows HUD. Enhanced with error handling, accessibility, and configuration options.

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

// ==UserScript==
// @name         Enhanced Smooth AutoScroll
// @version      2.0
// @description  Press 's' to toggle smooth auto-scroll. '[', ']' adjust speed (negative = scroll up). 'h' hides/shows HUD. Enhanced with error handling, accessibility, and configuration options.
// @author       NAABO
// @match        *://*/*
// @grant        none
// @run-at       document-idle
// @namespace https://gf.qytechs.cn/users/1513610
// ==/UserScript==

/**
 * Enhanced Auto-Scroll UserScript
 * Hotkeys:
 *   S → toggle auto-scroll
 *   [/] → adjust speed (unlimited, can be negative)
 *   H → hide/show HUD
 *   R → reset to default speed
 *   +/- → adjust speed step
 */

(function () {
  'use strict';

  /************* Configuration & Constants *************/
  const CONFIG = {
    STORAGE_KEY: 'enhanced_autoscroll_config',
    DEFAULT_SPEED: 100, // px per second (downward)
    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, // ms
    FLASH_DURATION: 1500, // ms
    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,
  };

  /************* Configuration Management *************/
  /**
   * Load configuration from localStorage with error handling
   */
  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, using defaults', error);
    }
  }

  /**
   * Save configuration to localStorage with error handling
   */
  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);
    }
  }

  /************* Utility Functions *************/
  /**
   * Check if user is currently typing in an input field
   */
  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 (error) {
      console.warn('Enhanced AutoScroll: Error checking typing state', error);
      return false;
    }
  }

  /**
   * Check if user prefers reduced motion
   */
  function prefersReducedMotion() {
    try {
      return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    } catch (error) {
      return false;
    }
  }

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

  /**
   * Safe requestAnimationFrame with fallback
   */
  function safeRequestAnimationFrame(callback) {
    try {
      return requestAnimationFrame(callback);
    } catch (error) {
      console.warn('Enhanced AutoScroll: requestAnimationFrame not available, using setTimeout');
      return setTimeout(callback, 16); // ~60fps fallback
    }
  }

  /**
   * Safe cancelAnimationFrame with fallback
   */
  function safeCancelAnimationFrame(id) {
    try {
      cancelAnimationFrame(id);
    } catch (error) {
      clearTimeout(id);
    }
  }

  /************* Scrolling Engine *************/
  /**
   * Main animation loop with error handling and accessibility checks
   */
  function step(now) {
    try {
      if (state.terminated) return;

      // Check for reduced motion preference
      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); // Cap delta time
      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;

        // Check boundaries
        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 {
          // Smooth scrolling with bounds checking
          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();
    }
  }

  /**
   * Start the animation loop with error handling
   */
  function startLoop() {
    try {
      if (!state.rafId) {
        state.lastFrameTime = null;
        state.rafId = safeRequestAnimationFrame(step);
      }
    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to start animation loop', error);
    }
  }

  /**
   * Stop the animation loop and clean up
   */
  function stopLoop() {
    try {
      if (state.rafId) {
        safeCancelAnimationFrame(state.rafId);
        state.rafId = null;
      }
      state.lastFrameTime = null;
    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to stop animation loop', error);
    }
  }

  /************* Controls *************/
  /**
   * Toggle scroll state with optional force parameter
   */
  function toggleEnabled(forceState) {
    try {
      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();
    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to toggle enabled state', error);
    }
  }

  /**
   * Adjust scroll speed with bounds checking
   */
  function adjustSpeed(delta) {
    try {
      const oldSpeed = state.speed;
      state.speed += delta;

      // Visual feedback with speed change
      const direction = state.speed >= 0 ? '↓' : '↑';
      const speedText = `Speed: ${Math.abs(state.speed)} px/s ${direction}`;

      updateHUD();
      flashHUD(speedText);
      saveConfig();

      // If we were scrolling, show immediate feedback
      if (state.enabled && Math.sign(oldSpeed) !== Math.sign(state.speed)) {
        flashHUD(`Direction changed! ${speedText}`);
      }
    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to adjust speed', error);
    }
  }

  /**
   * Reset speed to default
   */
  function resetSpeed() {
    try {
      state.speed = CONFIG.DEFAULT_SPEED;
      updateHUD();
      flashHUD(`Speed reset to ${CONFIG.DEFAULT_SPEED} px/s`);
      saveConfig();
    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to reset speed', error);
    }
  }


  /************* HUD Management *************/
  /**
   * Get HUD position styles based on current position setting
   */
  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'];
  }

  /**
   * Create the HUD with enhanced styling and error handling
   */
  function createHUD() {
    try {
      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" style="
            background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2);
            color:#fff; padding:4px 8px; border-radius:4px; font-size:11px;
            cursor:pointer; transition:all 0.2s ease;
          " title="Toggle scrolling">S</button>
          <button class="hud-btn" data-action="speed-down" style="
            background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2);
            color:#fff; padding:4px 8px; border-radius:4px; font-size:11px;
            cursor:pointer; transition:all 0.2s ease;
          " title="Decrease speed">[</button>
          <button class="hud-btn" data-action="speed-up" style="
            background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2);
            color:#fff; padding:4px 8px; border-radius:4px; font-size:11px;
            cursor:pointer; transition:all 0.2s ease;
          " title="Increase speed">]</button>
          <button class="hud-btn" data-action="reset" style="
            background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2);
            color:#fff; padding:4px 8px; border-radius:4px; font-size:11px;
            cursor:pointer; transition:all 0.2s ease;
          " title="Reset speed">R</button>
          <button class="hud-btn" data-action="hide-hud" style="
            background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2);
            color:#fff; padding:4px 8px; border-radius:4px; font-size:11px;
            cursor:pointer; transition:all 0.2s ease;
          " title="Hide HUD">H</button>
          <button class="hud-btn" data-action="step-up" style="
            background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2);
            color:#fff; padding:4px 8px; border-radius:4px; font-size:11px;
            cursor:pointer; transition:all 0.2s ease;
          " title="Increase speed step">+</button>
          <button class="hud-btn" data-action="step-down" style="
            background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2);
            color:#fff; padding:4px 8px; border-radius:4px; font-size:11px;
            cursor:pointer; transition:all 0.2s ease;
          " title="Decrease speed step">-</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);

      // Enhanced close button interaction
      const closeBtn = state.hud.querySelector('#hud-close');
      closeBtn.addEventListener('click', shutdownScript);
      closeBtn.addEventListener('mouseenter', () => closeBtn.style.opacity = '1');
      closeBtn.addEventListener('mouseleave', () => closeBtn.style.opacity = '0.7');

      // Set up HUD button interactions
      setupHUDButtons();

    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to create HUD', error);
    }
  }

  /**
   * Set up HUD button event listeners with direct function calls
   */
  function setupHUDButtons() {
    try {
      const buttons = state.hud.querySelectorAll('.hud-btn');

      buttons.forEach(button => {
        const action = button.getAttribute('data-action');

        // Add hover effects
        button.addEventListener('mouseenter', () => {
          button.style.background = 'rgba(255,255,255,0.2)';
          button.style.borderColor = 'rgba(255,255,255,0.4)';
        });

        button.addEventListener('mouseleave', () => {
          button.style.background = 'rgba(255,255,255,0.1)';
          button.style.borderColor = 'rgba(255,255,255,0.2)';
        });

        // Add click handlers - direct function calls, no key simulation
        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 += 1;
                saveConfig();
                updateHUD();
                flashHUD(`Speed step: ${state.speedStep}`);
              }
              break;
            case 'step-down':
              if (state.speedStep > CONFIG.MIN_SPEED_STEP) {
                state.speedStep -= 1;
                saveConfig();
                updateHUD();
                flashHUD(`Speed step: ${state.speedStep}`);
              }
              break;
          }
        });
      });
    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to setup HUD buttons', error);
    }
  }

  /**
   * Update HUD content with current state
   */
  function updateHUD() {
    try {
      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');

      // Update status
      if (state.enabled) {
        const direction = state.speed >= 0 ? '↓' : '↑';
        statusEl.textContent = `SCROLLING ${direction}`;
        statusEl.style.color = '#4ade80';
      } else {
        statusEl.textContent = 'PAUSED';
        statusEl.style.color = '#ef4444';
      }

      // Update speed info
      speedEl.textContent = `Speed: ${Math.abs(state.speed)} px/s (Step: ${state.speedStep})`;

      // Update config info
      const configParts = [];
      if (state.respectsReducedMotion && prefersReducedMotion()) configParts.push('Reduced motion');
      configEl.textContent = configParts.join(' • ');

      // Update button states
      updateButtonStates();

    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to update HUD', error);
    }
  }

  /**
   * Update button visual states based on current settings
   */
  function updateButtonStates() {
    try {
      if (!state.hud) return;

      const buttons = state.hud.querySelectorAll('.hud-btn');
      buttons.forEach(button => {
        const action = button.getAttribute('data-action');

        // Highlight active states
        if (action === 'toggle' && state.enabled) {
          button.style.background = 'rgba(76, 175, 80, 0.3)';
          button.style.borderColor = 'rgba(76, 175, 80, 0.6)';
        } else if (action === 'toggle' && !state.enabled) {
          button.style.background = 'rgba(244, 67, 54, 0.3)';
          button.style.borderColor = 'rgba(244, 67, 54, 0.6)';
        }
      });
    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to update button states', error);
    }
  }

  /**
   * Flash HUD with temporary message
   */
  function flashHUD(text, duration = CONFIG.FLASH_DURATION) {
    try {
      if (!state.hud) createHUD();
      if (!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(() => {
        try {
          updateHUD();
          state.flashTimeout = null;
        } catch (error) {
          console.error('Enhanced AutoScroll: Error in flash timeout', error);
        }
      }, duration);

    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to flash HUD', error);
    }
  }

  /************* Enhanced Shutdown *************/
  /**
   * Comprehensive script shutdown with cleanup
   */
  function shutdownScript() {
    try {
      if (state.terminated) return;
      state.terminated = true;

      // Stop all animations and timers
      stopLoop();
      state.enabled = false;

      // Clear all timeouts
      if (state.flashTimeout) {
        clearTimeout(state.flashTimeout);
        state.flashTimeout = null;
      }
      if (state.keyDebounceTimeout) {
        clearTimeout(state.keyDebounceTimeout);
        state.keyDebounceTimeout = null;
      }

      // Remove all event listeners
      document.removeEventListener('keydown', onKeyDown);
      window.removeEventListener('beforeunload', shutdownScript);

      // Remove HUD with fade effect
      if (state.hud) {
        state.hud.style.transition = 'opacity 0.3s ease';
        state.hud.style.opacity = '0';
        setTimeout(() => {
          if (state.hud && state.hud.parentNode) {
            state.hud.remove();
          }
          state.hud = null;
        }, 300);
      }

      console.log('Enhanced AutoScroll: Script terminated successfully');
    } catch (error) {
      console.error('Enhanced AutoScroll: Error during shutdown', error);
    }
  }




  /************* Enhanced Key Handling *************/
  /**
   * Debounced key handler with comprehensive hotkey support
   */
  const onKeyDown = debounce(function (e) {
    try {
      if (isTyping(e) || state.terminated) return;

      const key = e.key.toLowerCase();
      let handled = false;

      switch (key) {
        case 's':
          e.preventDefault();
          toggleEnabled();
          handled = true;
          break;

        case '[':
          e.preventDefault();
          adjustSpeed(-state.speedStep);
          handled = true;
          break;

        case ']':
          e.preventDefault();
          adjustSpeed(state.speedStep);
          handled = true;
          break;

        case 'h':
          e.preventDefault();
          state.hudVisible = !state.hudVisible;
          updateHUD();
          flashHUD(`HUD ${state.hudVisible ? 'shown' : 'hidden'}`);
          handled = true;
          break;

        case 'r':
          e.preventDefault();
          resetSpeed();
          handled = true;
          break;


        case '+':
        case '=':
          e.preventDefault();
          if (state.speedStep < CONFIG.MAX_SPEED_STEP) {
            state.speedStep += 1;
            saveConfig();
            updateHUD();
            flashHUD(`Speed step: ${state.speedStep}`);
          }
          handled = true;
          break;

        case '-':
        case '_':
          e.preventDefault();
          if (state.speedStep > CONFIG.MIN_SPEED_STEP) {
            state.speedStep -= 1;
            saveConfig();
            updateHUD();
            flashHUD(`Speed step: ${state.speedStep}`);
          }
          handled = true;
          break;
      }

      if (handled) {
        e.stopPropagation();
      }

    } catch (error) {
      console.error('Enhanced AutoScroll: Error in key handler', error);
    }
  }, CONFIG.DEBOUNCE_DELAY);

  /************* Enhanced Initialization *************/
  /**
   * Initialize the enhanced autoscroll with comprehensive setup
   */
  function init() {
    try {
      console.log('Enhanced AutoScroll: Initializing v2.0');

      // Load saved configuration
      loadConfig();

      // Create and update HUD
      createHUD();
      updateHUD();

      // Set up event listeners with error handling
      document.addEventListener('keydown', onKeyDown, { passive: false });
      window.addEventListener('beforeunload', shutdownScript, { passive: true });


      // Show initialization message
      flashHUD('Enhanced AutoScroll ready! Press S to start', 2000);

      console.log('Enhanced AutoScroll: Initialization complete');

    } catch (error) {
      console.error('Enhanced AutoScroll: Failed to initialize', error);
      // Try to show error in a basic way
      try {
        alert('Enhanced AutoScroll failed to initialize. Check console for details.');
      } catch (alertError) {
        // If even alert fails, just log
        console.error('Enhanced AutoScroll: Critical initialization failure');
      }
    }
  }

  // Initialize when DOM is ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址