// ==UserScript== // @name 高级自动滚动控制器 // @namespace http://tampermonkey.net/ // @version 1.4 // @description 新增显示/隐藏面板功能(快捷键Alt+R),新增最小化快捷键(Alt+M),优化面板样式 // @author yangwenren // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; // 全局变量 let isScrolling = false; let scrollSpeed = 5; let scrollDirection = 'down'; let bottomAction = 'jump'; let panelMinimized = false; let panelHidden = false; let isDragging = false; let dragOffsetX = 0; let dragOffsetY = 0; let scrollInterval = null; let rafId = null; const MIN_SPEED = 1; const MAX_SPEED = 40; const SAFE_MARGIN = 15; const FIXED_PANEL_HEIGHT = 260; const MINIMIZED_HEIGHT = 45; const PANEL_WIDTH = 280; const MINIMIZED_WIDTH = 200; const TOGGLE_HOTKEY = 'Alt+R'; // 显示/隐藏快捷键 const MINIMIZE_HOTKEY = 'Alt+M'; // 最小化快捷键 // 创建控制界面 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: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.12); z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; width: ${PANEL_WIDTH}px; height: ${FIXED_PANEL_HEIGHT}px; user-select: none; will-change: transform, width, opacity; touch-action: none; transform: translateZ(0); transition: width 0.2s ease, box-shadow 0.2s ease, opacity 0.3s ease, padding-right 0.2s ease; overflow: hidden; padding-right: 0; `; // 标题栏 const titleBar = document.createElement('div'); titleBar.style.cssText = ` padding: 10px 18px; display: flex; justify-content: space-between; align-items: center; cursor: move; border-top-left-radius: 12px; border-top-right-radius: 12px; transition: background-color 0.2s ease; height: 45px; box-sizing: border-box; `; titleBar.classList.add('drag-handle'); titleBar.addEventListener('mousedown', () => { titleBar.style.opacity = '0.9'; }); titleBar.addEventListener('mouseup', () => { titleBar.style.opacity = '1'; }); const title = document.createElement('h3'); title.textContent = '高级滚动控制器'; title.style.cssText = 'margin: 0; font-size: 15px; font-weight: 500; letter-spacing: 0.3px; transition: font-size 0.2s ease;'; // 按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.gap = '6px'; // 最小化按钮 const minBtn = document.createElement('button'); minBtn.textContent = '−'; // 鼠标悬浮显示功能说明和快捷键 minBtn.title = `最小化面板(快捷键:${MINIMIZE_HOTKEY})`; minBtn.style.cssText = ` width: 26px; height: 26px; border: none; background: transparent; font-size: 20px; cursor: pointer; line-height: 1; padding: 0; border-radius: 50%; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; `; minBtn.addEventListener('mouseover', () => { minBtn.style.backgroundColor = 'rgba(0,0,0,0.08)'; }); minBtn.addEventListener('mouseout', () => { minBtn.style.backgroundColor = 'transparent'; }); minBtn.addEventListener('click', () => toggleMinimize(panel)); // 隐藏面板按钮 const hideBtn = document.createElement('button'); hideBtn.textContent = '⇨'; hideBtn.title = `隐藏面板(快捷键:${TOGGLE_HOTKEY})`; hideBtn.style.cssText = ` width: 26px; height: 26px; border: none; background: transparent; font-size: 16px; cursor: pointer; line-height: 1; padding: 0; border-radius: 50%; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; `; hideBtn.addEventListener('mouseover', () => { hideBtn.style.backgroundColor = 'rgba(0,0,0,0.08)'; }); hideBtn.addEventListener('mouseout', () => { hideBtn.style.backgroundColor = 'transparent'; }); hideBtn.addEventListener('click', () => togglePanelVisibility(panel)); buttonContainer.append(minBtn, hideBtn); titleBar.append(title, buttonContainer); panel.appendChild(titleBar); // 内容区域 const contentArea = document.createElement('div'); contentArea.id = 'panelContent'; contentArea.style.cssText = ` padding: 18px; transition: display 0.2s ease; height: calc(${FIXED_PANEL_HEIGHT}px - 45px); box-sizing: border-box; overflow: hidden; `; // 速度控制 const speedDiv = document.createElement('div'); speedDiv.style.cssText = ` margin-bottom: 15px; display: flex; flex-direction: column; gap: 6px; `; const speedLabel = document.createElement('label'); speedLabel.textContent = `滚动速度: ${scrollSpeed}`; speedLabel.id = 'speedLabel'; speedLabel.style.cssText = ` display: block; font-size: 14px; font-weight: 400; `; const speedSlider = document.createElement('input'); speedSlider.type = 'range'; speedSlider.min = MIN_SPEED; speedSlider.max = MAX_SPEED; speedSlider.value = scrollSpeed; speedSlider.style.cssText = ` width: 100%; height: 6px; -webkit-appearance: none; appearance: none; border-radius: 3px; outline: none; `; speedSlider.style.setProperty('-webkit-slider-thumb', ` -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #4CAF50; cursor: pointer; transition: all 0.2s ease; `); 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.cssText = ` margin-bottom: 15px; display: flex; align-items: center; gap: 12px; `; const directionLabel = document.createElement('label'); directionLabel.textContent = '滚动方向:'; directionLabel.style.cssText = ` width: 85px; font-size: 14px; font-weight: 400; `; const directionSelect = document.createElement('select'); directionSelect.style.cssText = ` flex: 1; padding: 7px 12px; border-radius: 6px; border: 1px solid; font-size: 14px; background-color: transparent; cursor: pointer; transition: all 0.2s ease; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; background-size: 14px; padding-right: 35px; `; directionSelect.addEventListener('mouseover', () => { directionSelect.style.borderColor = 'rgba(0,0,0,0.2)'; }); directionSelect.addEventListener('mouseout', () => { updateTheme(); }); 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.cssText = ` margin-bottom: 15px; display: flex; align-items: center; gap: 12px; `; const actionLabel = document.createElement('label'); actionLabel.textContent = '底部行为:'; actionLabel.style.cssText = ` width: 85px; font-size: 14px; font-weight: 400; `; const actionSelect = document.createElement('select'); actionSelect.style.cssText = ` flex: 1; padding: 7px 12px; border-radius: 6px; border: 1px solid; font-size: 14px; background-color: transparent; cursor: pointer; transition: all 0.2s ease; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; background-size: 14px; padding-right: 35px; `; actionSelect.addEventListener('mouseover', () => { actionSelect.style.borderColor = 'rgba(0,0,0,0.2)'; }); actionSelect.addEventListener('mouseout', () => { updateTheme(); }); 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.cssText = ` display: flex; gap: 10px; `; const startBtn = document.createElement('button'); startBtn.id = 'startScrollBtn'; startBtn.textContent = '开始滚动'; startBtn.style.cssText = ` flex: 1; padding: 8px 12px; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.1); `; startBtn.addEventListener('mouseover', () => { startBtn.style.transform = 'translateY(-1px)'; startBtn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.15)'; }); startBtn.addEventListener('mouseout', () => { startBtn.style.transform = 'translateY(0)'; startBtn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)'; }); const stopBtn = document.createElement('button'); stopBtn.id = 'stopScrollBtn'; stopBtn.textContent = '停止滚动'; stopBtn.style.cssText = ` flex: 1; padding: 8px 12px; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.1); opacity: 0.7; `; stopBtn.disabled = true; stopBtn.addEventListener('mouseover', () => { if (!stopBtn.disabled) { stopBtn.style.transform = 'translateY(-1px)'; stopBtn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.15)'; } }); stopBtn.addEventListener('mouseout', () => { if (!stopBtn.disabled) { stopBtn.style.transform = 'translateY(0)'; stopBtn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)'; } }); 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', () => { if (!panelHidden) checkBounds(panel); }); // 初始化快捷键 initHotkeys(panel); return panel; } // 切换面板显示/隐藏状态 function togglePanelVisibility(panel) { panelHidden = !panelHidden; if (panelHidden) { panel.style.opacity = '0'; panel.style.pointerEvents = 'none'; const hideBtn = panel.querySelector('button:last-of-type'); if (hideBtn) hideBtn.textContent = '⇦'; } else { panel.style.opacity = '1'; panel.style.pointerEvents = 'auto'; const hideBtn = panel.querySelector('button:last-of-type'); if (hideBtn) hideBtn.textContent = '⇨'; checkBounds(panel); } } // 切换最小化状态 function toggleMinimize(panel) { if (panelHidden) return; const content = document.getElementById('panelContent'); const minBtn = panel.querySelector('button:first-of-type'); const title = panel.querySelector('h3'); const buttonContainer = panel.querySelector('.drag-handle > div'); // 按钮容器 panelMinimized = !panelMinimized; if (panelMinimized) { content.style.display = 'none'; panel.style.width = `${MINIMIZED_WIDTH}px`; panel.style.height = `${MINIMIZED_HEIGHT}px`; panel.style.paddingRight = '0'; minBtn.textContent = '+'; title.style.fontSize = '15px'; buttonContainer.style.gap = '1px'; } else { content.style.display = 'block'; panel.style.width = `${PANEL_WIDTH}px`; panel.style.height = `${FIXED_PANEL_HEIGHT}px`; panel.style.paddingRight = '0'; minBtn.textContent = '−'; title.style.fontSize = '15px'; buttonContainer.style.gap = '6px'; } checkBounds(panel); } // 初始化所有快捷键 function initHotkeys(panel) { document.addEventListener('keydown', (e) => { // 显示/隐藏面板:Alt+R if (e.altKey && e.key.toLowerCase() === 'r') { e.preventDefault(); togglePanelVisibility(panel); } // 最小化/展开面板:Alt+M if (e.altKey && e.key.toLowerCase() === 'm') { e.preventDefault(); toggleMinimize(panel); } }); // 控制台提示所有快捷键 console.log(`高级滚动控制器快捷键:`); console.log(`- ${TOGGLE_HOTKEY}:显示/隐藏面板`); console.log(`- ${MINIMIZE_HOTKEY}:最小化/展开面板`); } // 检查面板位置 function checkBounds(panel) { const panelHeight = panelMinimized ? MINIMIZED_HEIGHT : FIXED_PANEL_HEIGHT; const panelWidth = panelMinimized ? MINIMIZED_WIDTH : PANEL_WIDTH; 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 + panelWidth > viewportWidth - SAFE_MARGIN) { newLeft = viewportWidth - panelWidth - 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 initDragEvents(panel, handle) { let panelInitialRect; handle.addEventListener('mousedown', (e) => { if (panelHidden) return; e.preventDefault(); e.stopPropagation(); isDragging = true; panelInitialRect = panel.getBoundingClientRect(); dragOffsetX = e.clientX - panelInitialRect.left; dragOffsetY = e.clientY - panelInitialRect.top; document.body.style.cursor = 'grabbing'; handle.style.cursor = 'grabbing'; document.body.style.userSelect = 'none'; panel.style.boxShadow = '0 6px 25px rgba(0,0,0,0.15)'; }); document.addEventListener('mousemove', (e) => { if (!isDragging || panelHidden) return; e.preventDefault(); if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { const newLeft = e.clientX - dragOffsetX; const newTop = e.clientY - dragOffsetY; panel.style.transform = `translate( ${newLeft - panelInitialRect.left}px, ${newTop - panelInitialRect.top}px ) translateZ(0)`; }); }); function endDrag() { if (isDragging) { isDragging = false; cancelAnimationFrame(rafId); rafId = null; document.body.style.cursor = ''; handle.style.cursor = 'move'; document.body.style.userSelect = ''; panel.style.boxShadow = '0 4px 20px rgba(0,0,0,0.12)'; 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('mouseup', endDrag); document.addEventListener('mouseleave', endDrag); } // 停止滚动函数 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 updateTheme() { const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const panel = document.getElementById('scrollControlPanel'); if (!panel) return; const titleBar = panel.querySelector('.drag-handle'); const title = panel.querySelector('h3'); const buttons = panel.querySelectorAll('.drag-handle button'); const labels = panel.querySelectorAll('label'); const selects = panel.querySelectorAll('select'); const startBtn = document.getElementById('startScrollBtn'); const stopBtn = document.getElementById('stopScrollBtn'); const speedSlider = panel.querySelector('input[type="range"]'); if (isDarkMode) { panel.style.backgroundColor = 'rgba(28, 28, 30, 0.98)'; panel.style.border = '1px solid rgba(70, 70, 75, 0.5)'; titleBar.style.backgroundColor = 'rgba(44, 44, 46, 0.9)'; title.style.color = '#f5f5f7'; buttons.forEach(btn => btn.style.color = '#d2d2d7'); labels.forEach(label => label.style.color = '#e4e4e7'); selects.forEach(select => { select.style.backgroundColor = 'rgba(44, 44, 46, 0.8)'; select.style.color = '#e4e4e7'; select.style.borderColor = 'rgba(70, 70, 75, 0.8)'; }); speedSlider.style.backgroundColor = 'rgba(70, 70, 75, 0.5)'; speedSlider.style.setProperty('-webkit-slider-thumb', ` -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #4CAF50; cursor: pointer; transition: all 0.2s ease; `); if (startBtn) startBtn.style.backgroundColor = '#43a047'; if (stopBtn) stopBtn.style.backgroundColor = '#e53935'; } else { panel.style.backgroundColor = 'rgba(255, 255, 255, 0.98)'; panel.style.border = '1px solid rgba(220, 220, 225, 0.8)'; titleBar.style.backgroundColor = 'rgba(249, 249, 250, 0.9)'; title.style.color = '#1d1d1f'; buttons.forEach(btn => btn.style.color = '#6e6e73'); labels.forEach(label => label.style.color = '#1d1d1f'); selects.forEach(select => { select.style.backgroundColor = 'rgba(249, 249, 250, 0.8)'; select.style.color = '#1d1d1f'; select.style.borderColor = 'rgba(220, 220, 225, 0.8)'; }); speedSlider.style.backgroundColor = 'rgba(220, 220, 225, 0.8)'; speedSlider.style.setProperty('-webkit-slider-thumb', ` -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #4CAF50; cursor: pointer; transition: all 0.2s ease; `); 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); } // 初始化 if (document.readyState === 'complete' || document.readyState === 'interactive') { createControlPanel(); } else { document.addEventListener('DOMContentLoaded', createControlPanel); setTimeout(createControlPanel, 3000); } })();