// ==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();
})();