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