// ==UserScript== // @name 高级自动滚动控制器 // @namespace http://tampermonkey.net/ // @version 1.2 // @description 支持调节滚动速度、方向、底部行为、精准拖动、自动适配日间/夜间模式,且面板可最小化的自动滚动工具 // @author yangwenren // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; // 全局变量 let isScrolling = false; let scrollSpeed = 5; // 默认滚动速度 let scrollDirection = 'down';// 滚动方向 (down/up) let bottomAction = 'jump'; // 底部行为 (jump/reverse/stop) let panelMinimized = false; // 面板是否最小化 let isDragging = false; // 是否正在拖动 let startX = 0; // 拖动开始时的鼠标X坐标 let startY = 0; // 拖动开始时的鼠标Y坐标 let initialLeft, initialTop; // 面板初始位置 let scrollInterval = null; // 滚动定时器 let rafId = null; // 用于requestAnimationFrame const MIN_SPEED = 1; // 最小速度调整为1 const MAX_SPEED = 40; // 最大速度调整为40 const SAFE_MARGIN = 10; // 安全边距(像素) const NORMAL_HEIGHT = 240; // 正常状态面板高度 const MINIMIZED_HEIGHT = 40; // 最小化状态面板高度 const PANEL_WIDTH = 250; // 面板宽度 // 创建控制界面 function createControlPanel() { // 主面板容器 - 默认定位在右下角 const panel = document.createElement('div'); panel.id = 'scrollControlPanel'; panel.style.cssText = ` position: fixed; bottom: ${SAFE_MARGIN}px; right: ${SAFE_MARGIN}px; border-radius: 8px; box-shadow: 0 2px 15px rgba(0,0,0,0.2); z-index: 999999; font-family: Arial, sans-serif; width: ${PANEL_WIDTH}px; user-select: none; will-change: transform; touch-action: none; transform: translateZ(0); `; // 标题栏(拖动把手) const titleBar = document.createElement('div'); titleBar.style.cssText = ` padding: 8px 15px; display: flex; justify-content: space-between; align-items: center; cursor: move; `; titleBar.classList.add('drag-handle'); const title = document.createElement('h3'); title.textContent = '高级滚动控制器'; title.style.cssText = 'margin: 0; font-size: 15px;'; const minBtn = document.createElement('button'); minBtn.textContent = '−'; minBtn.style.cssText = ` width: 24px; height: 24px; border: none; background: transparent; font-size: 18px; cursor: pointer; line-height: 1; padding: 0; `; minBtn.addEventListener('click', toggleMinimize); titleBar.append(title, minBtn); panel.appendChild(titleBar); // 内容区域(可折叠) const contentArea = document.createElement('div'); contentArea.id = 'panelContent'; contentArea.style.padding = '15px'; // 速度控制 - 范围调整为1-40 const speedDiv = document.createElement('div'); speedDiv.style.marginBottom = '12px'; const speedLabel = document.createElement('label'); speedLabel.textContent = `滚动速度: ${scrollSpeed}`; speedLabel.id = 'speedLabel'; speedLabel.style.display = 'block'; speedLabel.style.marginBottom = '5px'; const speedSlider = document.createElement('input'); speedSlider.type = 'range'; speedSlider.min = MIN_SPEED; // 1 speedSlider.max = MAX_SPEED; // 40 speedSlider.value = scrollSpeed; speedSlider.style.width = '100%'; speedSlider.addEventListener('input', function() { scrollSpeed = parseInt(this.value); document.getElementById('speedLabel').textContent = `滚动速度: ${scrollSpeed}`; if (isScrolling) updateScrollInterval(); }); speedDiv.append(speedLabel, speedSlider); contentArea.appendChild(speedDiv); // 方向控制 const directionDiv = document.createElement('div'); directionDiv.style.marginBottom = '12px'; directionDiv.style.display = 'flex'; directionDiv.style.alignItems = 'center'; directionDiv.style.gap = '10px'; const directionLabel = document.createElement('label'); directionLabel.textContent = '滚动方向:'; directionLabel.style.width = '80px'; const directionSelect = document.createElement('select'); directionSelect.style.flex = '1'; directionSelect.innerHTML = ` `; directionSelect.value = scrollDirection; directionSelect.addEventListener('change', function() { scrollDirection = this.value; if (isScrolling) updateScrollInterval(); }); directionDiv.append(directionLabel, directionSelect); contentArea.appendChild(directionDiv); // 底部行为控制 const actionDiv = document.createElement('div'); actionDiv.style.marginBottom = '15px'; actionDiv.style.display = 'flex'; actionDiv.style.alignItems = 'center'; actionDiv.style.gap = '10px'; const actionLabel = document.createElement('label'); actionLabel.textContent = '底部行为:'; actionLabel.style.width = '80px'; const actionSelect = document.createElement('select'); actionSelect.style.flex = '1'; actionSelect.innerHTML = ` `; actionSelect.value = bottomAction; actionSelect.addEventListener('change', function() { bottomAction = this.value; if (isScrolling) updateScrollInterval(); }); actionDiv.append(actionLabel, actionSelect); contentArea.appendChild(actionDiv); // 控制按钮 const buttonDiv = document.createElement('div'); buttonDiv.style.display = 'flex'; buttonDiv.style.gap = '8px'; const startBtn = document.createElement('button'); startBtn.id = 'startScrollBtn'; startBtn.textContent = '开始滚动'; startBtn.style.cssText = ` flex: 1; padding: 6px; color: white; border: none; border-radius: 4px; cursor: pointer; `; const stopBtn = document.createElement('button'); stopBtn.id = 'stopScrollBtn'; stopBtn.textContent = '停止滚动'; stopBtn.style.cssText = ` flex: 1; padding: 6px; color: white; border: none; border-radius: 4px; cursor: pointer; opacity: 0.7; `; stopBtn.disabled = true; startBtn.addEventListener('click', () => { if (!isScrolling) { startScrolling(); isScrolling = true; startBtn.disabled = true; stopBtn.disabled = false; stopBtn.style.opacity = '1'; } }); stopBtn.addEventListener('click', () => { stopScrolling(); isScrolling = false; startBtn.disabled = false; stopBtn.disabled = true; stopBtn.style.opacity = '0.7'; }); buttonDiv.append(startBtn, stopBtn); contentArea.appendChild(buttonDiv); panel.appendChild(contentArea); document.body.appendChild(panel); // 初始化主题并监听主题变化 updateTheme(); window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme); // 初始化拖动功能 initDragEvents(panel, titleBar); // 窗口大小变化时重新检查位置 window.addEventListener('resize', () => { checkBounds(panel); }); return panel; } // 检查并修正面板位置在可视范围内 function checkBounds(panel) { const panelHeight = panelMinimized ? MINIMIZED_HEIGHT : NORMAL_HEIGHT; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const rect = panel.getBoundingClientRect(); let newLeft = rect.left; let newTop = rect.top; if (newLeft < SAFE_MARGIN) newLeft = SAFE_MARGIN; else if (newLeft + PANEL_WIDTH > viewportWidth - SAFE_MARGIN) { newLeft = viewportWidth - PANEL_WIDTH - SAFE_MARGIN; } if (newTop < SAFE_MARGIN) newTop = SAFE_MARGIN; else if (newTop + panelHeight > viewportHeight - SAFE_MARGIN) { newTop = viewportHeight - panelHeight - SAFE_MARGIN; } if (newLeft !== rect.left || newTop !== rect.top) { panel.style.left = `${newLeft}px`; panel.style.top = `${newTop}px`; panel.style.right = 'auto'; panel.style.bottom = 'auto'; panel.style.transform = 'translateZ(0)'; } } // 停止滚动函数 function stopScrolling() { if (isScrolling && scrollInterval) { clearInterval(scrollInterval); scrollInterval = null; } isScrolling = false; const startBtn = document.getElementById('startScrollBtn'); const stopBtn = document.getElementById('stopScrollBtn'); if (startBtn) startBtn.disabled = false; if (stopBtn) { stopBtn.disabled = true; stopBtn.style.opacity = '0.7'; } } // 初始化拖动事件 function initDragEvents(panel, handle) { handle.addEventListener('mousedown', (e) => { e.preventDefault(); e.stopPropagation(); isDragging = true; const rect = panel.getBoundingClientRect(); startX = e.clientX; startY = e.clientY; initialLeft = rect.left; initialTop = rect.top; document.body.style.cursor = 'grabbing'; handle.style.cursor = 'grabbing'; document.body.style.userSelect = 'none'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; e.preventDefault(); if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; panel.style.transform = `translate( ${deltaX}px, ${deltaY}px ) translateZ(0)`; }); }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; cancelAnimationFrame(rafId); rafId = null; document.body.style.cursor = ''; handle.style.cursor = 'move'; document.body.style.userSelect = ''; const rect = panel.getBoundingClientRect(); panel.style.left = `${rect.left}px`; panel.style.top = `${rect.top}px`; panel.style.transform = 'translateZ(0)'; checkBounds(panel); } }); document.addEventListener('mouseleave', () => { if (isDragging) { isDragging = false; cancelAnimationFrame(rafId); rafId = null; document.body.style.cursor = ''; handle.style.cursor = 'move'; document.body.style.userSelect = ''; checkBounds(panel); } }); } // 切换最小化/展开状态 function toggleMinimize() { const panel = document.getElementById('scrollControlPanel'); const content = document.getElementById('panelContent'); const minBtn = panel.querySelector('button'); panelMinimized = !panelMinimized; if (panelMinimized) { content.style.display = 'none'; panel.style.width = 'auto'; minBtn.textContent = '+'; } else { content.style.display = 'block'; panel.style.width = `${PANEL_WIDTH}px`; minBtn.textContent = '−'; } checkBounds(panel); } // 主题更新函数 function updateTheme() { const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const panel = document.getElementById('scrollControlPanel'); const titleBar = panel.querySelector('.drag-handle'); const title = panel.querySelector('h3'); const minBtn = panel.querySelector('button'); const labels = panel.querySelectorAll('label'); const selects = panel.querySelectorAll('select'); const startBtn = document.getElementById('startScrollBtn'); const stopBtn = document.getElementById('stopScrollBtn'); if (isDarkMode) { panel.style.backgroundColor = 'rgba(30, 30, 30, 0.95)'; panel.style.border = '1px solid #444'; titleBar.style.borderBottom = '1px solid #444'; title.style.color = '#f0f0f0'; minBtn.style.color = '#ccc'; labels.forEach(label => label.style.color = '#f0f0f0'); selects.forEach(select => { select.style.backgroundColor = '#444'; select.style.color = '#fff'; select.style.border = '1px solid #666'; }); if (startBtn) startBtn.style.backgroundColor = '#4CAF50'; if (stopBtn) stopBtn.style.backgroundColor = '#f44336'; } else { panel.style.backgroundColor = 'rgba(255, 255, 255, 0.95)'; panel.style.border = '1px solid #ccc'; titleBar.style.borderBottom = '1px solid #eee'; title.style.color = '#333'; minBtn.style.color = '#666'; labels.forEach(label => label.style.color = '#333'); selects.forEach(select => { select.style.backgroundColor = '#fff'; select.style.color = '#333'; select.style.border = '1px solid #ccc'; }); if (startBtn) startBtn.style.backgroundColor = '#4CAF50'; if (stopBtn) stopBtn.style.backgroundColor = '#f44336'; } } // 更新滚动间隔 function updateScrollInterval() { if (scrollInterval) { clearInterval(scrollInterval); scrollInterval = null; } startScrolling(); } // 滚动逻辑实现 function startScrolling() { if (scrollInterval) return; scrollInterval = setInterval(() => { const currentPos = window.scrollY; const windowHeight = window.innerHeight; const docHeight = Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight ); const atBottom = currentPos + windowHeight >= docHeight - 50; const atTop = currentPos <= 50; let newPos = currentPos; if (scrollDirection === 'down') { newPos = currentPos + scrollSpeed; if (atBottom) { switch (bottomAction) { case 'jump': newPos = 0; break; case 'reverse': scrollDirection = 'up'; newPos = currentPos - scrollSpeed; break; case 'stop': stopScrolling(); return; } } } else { newPos = currentPos - scrollSpeed; if (atTop) { switch (bottomAction) { case 'jump': newPos = docHeight - windowHeight; break; case 'reverse': scrollDirection = 'down'; newPos = currentPos + scrollSpeed; break; case 'stop': stopScrolling(); return; } } } window.scrollTo(0, newPos); }, 20); } // 初始化 createControlPanel(); })();