单击中键激活/关闭滚动。激活后,页面会根据鼠标与初始点的距离和方向持续自动滚动。
// ==UserScript==
// @name Middle Click Scroll
// @namespace http://tampermonkey.net/
// @version 3.0
// @description 单击中键激活/关闭滚动。激活后,页面会根据鼠标与初始点的距离和方向持续自动滚动。
// @description:en Click middle mouse button to toggle scroll mode. Once active, the page continuously scrolls based on the mouse's distance and direction from the initial point.
// @author Xy2002
// @match *://*/*
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
/** @type {boolean} - 标记滚动模式是否激活 */
let isScrollModeActive = false;
/** @type {number} - 激活模式时鼠标的初始Y坐标 */
let startY = 0;
/** @type {number} - 当前需要持续滚动的量 */
let scrollAmount = 0;
/** @type {number | null} - 用于存储 requestAnimationFrame 的 ID */
let animationFrameId = null;
/** @type {number} - 灵敏度/速度因子,可以调整这个值来改变滚动的快慢 */
const SENSITIVITY_FACTOR = 0.15;
// --- 创建并注入视觉指示器的样式 ---
GM_addStyle(`
#mmb-scroll-indicator {
position: fixed;
width: 32px;
height: 32px;
background-color: rgba(0, 0, 0, 0.5);
border: 2px solid white;
border-radius: 50%;
z-index: 99999999;
pointer-events: none; /* 让鼠标事件可以穿透这个元素 */
display: none; /* 默认隐藏 */
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>');
background-size: 80%;
background-repeat: no-repeat;
background-position: center;
}
`);
// --- 创建视觉指示器元素 ---
const indicator = document.createElement('div');
indicator.id = 'mmb-scroll-indicator';
document.body.appendChild(indicator);
/**
* 持续滚动的核心函数
*/
const performScroll = () => {
if (!isScrollModeActive) {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
return;
}
if (scrollAmount !== 0) {
window.scrollBy(0, scrollAmount);
}
animationFrameId = requestAnimationFrame(performScroll);
};
// --- 新增:激活滚动模式的函数 ---
const activateScrollMode = (e) => {
isScrollModeActive = true;
startY = e.clientY;
document.body.style.cursor = 'all-scroll';
indicator.style.left = `${e.clientX - 16}px`;
indicator.style.top = `${e.clientY - 16}px`;
indicator.style.display = 'block';
// 启动滚动循环
performScroll();
};
// --- 新增:封装了退出滚动模式的逻辑,方便复用 ---
const deactivateScrollMode = () => {
isScrollModeActive = false;
document.body.style.cursor = 'default';
indicator.style.display = 'none';
scrollAmount = 0; // 重置滚动量
// 循环会在下一次 performScroll 检查时自动停止
};
/**
* 鼠标点击事件处理,用于切换或退出模式
* @param {MouseEvent} e
*/
const handleMouseDown = (e) => {
// e.button === 0 -> 左键
// e.button === 1 -> 中键
// e.button === 2 -> 右键
// 如果是中键点击,则切换滚动模式
if (e.button === 1) {
e.preventDefault(); // 阻止中键点击的默认行为 (例如在链接上点击会新建标签页)
if (isScrollModeActive) {
deactivateScrollMode();
} else {
activateScrollMode(e);
}
return; // 处理完毕,直接返回
}
// ---【核心修改】---
// 如果当前是滚动模式,并且点击的是左键或右键,则退出滚动模式
if (isScrollModeActive && (e.button === 0 || e.button === 2)) {
e.preventDefault(); // 阻止默认行为,如右键菜单或拖拽选中文本
deactivateScrollMode();
}
};
/**
* 鼠标移动事件处理,仅用于更新滚动速度和方向
* @param {MouseEvent} e
*/
const handleMouseMove = (e) => {
if (!isScrollModeActive) return;
const currentY = e.clientY;
const deltaY = currentY - startY;
scrollAmount = deltaY * SENSITIVITY_FACTOR;
};
// 绑定事件监听器 (将 handleMiddleClick 重命名为更通用的 handleMouseDown)
window.addEventListener('mousedown', handleMouseDown, true); // 使用捕获阶段以优先处理
window.addEventListener('mousemove', handleMouseMove, false);
})();