// ==UserScript== // @name 高级自动滚动控制器(精准拖动版) // @namespace http://tampermonkey.net/ // @version 1.1 // @description 支持精准拖动、自动适配日间/夜间模式的自动滚动工具 // @author yangwenren // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; // 全局变量 let isScrolling = false; let scrollSpeed = 5; // 滚动速度 let scrollDirection = 'down';// 滚动方向 (down/up) let bottomJump = true; // 底部是否跳转顶部 let panelMinimized = false; // 面板是否最小化 let isDragging = false; // 是否正在拖动 let dragOffsetX = 0; // 鼠标与面板左侧的偏移量 let dragOffsetY = 0; // 鼠标与面板顶部的偏移量 let scrollInterval; const MIN_SPEED = 1; const MAX_SPEED = 20; // 创建控制界面 function createControlPanel() { // 主面板容器 const panel = document.createElement('div'); panel.id = 'scrollControlPanel'; panel.style.cssText = ` position: fixed; bottom: 20px; right: 20px; border-radius: 8px; box-shadow: 0 2px 15px rgba(0,0,0,0.2); z-index: 999999; font-family: Arial, sans-serif; width: 250px; transition: all 0.3s ease; user-select: none; /* 防止拖动时选中文本 */ `; // 标题栏(拖动把手) 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'; // 速度控制、方向控制、底部行为控制等内容(与之前版本相同) // 速度控制 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; speedSlider.max = MAX_SPEED; 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 jumpDiv = document.createElement('div'); jumpDiv.style.marginBottom = '15px'; jumpDiv.style.display = 'flex'; jumpDiv.style.alignItems = 'center'; jumpDiv.style.gap = '10px'; const jumpLabel = document.createElement('label'); jumpLabel.textContent = '底部行为:'; jumpLabel.style.width = '80px'; const jumpSelect = document.createElement('select'); jumpSelect.style.flex = '1'; jumpSelect.innerHTML = ` `; jumpSelect.value = bottomJump ? 'jump' : 'reverse'; jumpSelect.addEventListener('change', function() { bottomJump = this.value === 'jump'; if (isScrolling) updateScrollInterval(); }); jumpDiv.append(jumpLabel, jumpSelect); contentArea.appendChild(jumpDiv); // 控制按钮 const buttonDiv = document.createElement('div'); buttonDiv.style.display = 'flex'; buttonDiv.style.gap = '8px'; const startBtn = document.createElement('button'); startBtn.textContent = '开始滚动'; startBtn.style.cssText = ` flex: 1; padding: 6px; color: white; border: none; border-radius: 4px; cursor: pointer; `; const stopBtn = document.createElement('button'); 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', () => { if (isScrolling) { clearInterval(scrollInterval); 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); return panel; } // 初始化拖动事件 function initDragEvents(panel, handle) { // 鼠标按下时开始拖动 handle.addEventListener('mousedown', (e) => { // 防止事件冒泡和默认行为 e.preventDefault(); e.stopPropagation(); isDragging = true; // 获取面板当前位置 const panelRect = panel.getBoundingClientRect(); // 计算鼠标在面板内的偏移量(关键:确保拖动时鼠标位置相对面板不变) dragOffsetX = e.clientX - panelRect.left; dragOffsetY = e.clientY - panelRect.top; // 改变鼠标样式表示正在拖动 document.body.style.cursor = 'grabbing'; handle.style.cursor = 'grabbing'; }); // 鼠标移动时更新面板位置 document.addEventListener('mousemove', (e) => { if (!isDragging) return; e.preventDefault(); // 计算新位置(减去偏移量使鼠标保持在点击位置) const newX = e.clientX - dragOffsetX; const newY = e.clientY - dragOffsetY; // 限制面板不超出视口 const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const panelWidth = panel.offsetWidth; const panelHeight = panel.offsetHeight; // 计算边界值(留出5px边距) const maxX = viewportWidth - panelWidth - 5; const maxY = viewportHeight - panelHeight - 5; const constrainedX = Math.max(5, Math.min(newX, maxX)); const constrainedY = Math.max(5, Math.min(newY, maxY)); // 更新面板位置 panel.style.left = `${constrainedX}px`; panel.style.top = `${constrainedY}px`; // 清除bottom和right属性,避免位置冲突 panel.style.bottom = 'auto'; panel.style.right = 'auto'; }); // 鼠标释放时结束拖动 document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; // 恢复鼠标样式 document.body.style.cursor = ''; handle.style.cursor = 'move'; } }); // 鼠标离开窗口时结束拖动 document.addEventListener('mouseleave', () => { if (isDragging) { isDragging = false; document.body.style.cursor = ''; handle.style.cursor = 'move'; } }); } // 根据系统主题更新面板样式 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 = panel.querySelector('button:first-of-type'); const stopBtn = panel.querySelector('button:last-of-type'); 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'; }); startBtn.style.backgroundColor = '#4CAF50'; 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'; }); startBtn.style.backgroundColor = '#4CAF50'; stopBtn.style.backgroundColor = '#f44336'; } } // 切换最小化/展开状态 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 = '250px'; minBtn.textContent = '−'; } } // 更新滚动间隔 function updateScrollInterval() { clearInterval(scrollInterval); startScrolling(); } // 滚动逻辑实现 function startScrolling() { 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; if (scrollDirection === 'down') { newPos = currentPos + scrollSpeed; if (atBottom) { newPos = bottomJump ? 0 : currentPos - scrollSpeed; if (!bottomJump) scrollDirection = 'up'; } } else { newPos = currentPos - scrollSpeed; if (atTop) { newPos = bottomJump ? docHeight - windowHeight : currentPos + scrollSpeed; if (!bottomJump) scrollDirection = 'down'; } } window.scrollTo(0, newPos); }, 20); } // 初始化 createControlPanel(); })();