在浏览器中添加悬浮翻页按钮,实现瞬时滚动。兼容即刻、小红书等单页面应用(SPA)的内部区域滚动。
// ==UserScript==
// @name Instant Scroll
// @namespace http://tampermonkey.net/
// @version 2.5.1
// @description 在浏览器中添加悬浮翻页按钮,实现瞬时滚动。兼容即刻、小红书等单页面应用(SPA)的内部区域滚动。
// @author chen
// @match https://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 避免在非顶层窗口(如 iframe)中加载
if (window.top !== window.self) return;
// 创建悬浮容器
const container = document.createElement('div');
container.style.position = 'fixed';
// 最小 60px,首选屏幕高度的 8%,最大不超过 100px
container.style.bottom = 'clamp(60px, 8vh, 100px)';
// 最小 10px,首选屏幕宽度的 3%,最大不超过 40px
container.style.left = 'clamp(10px, 3vw, 40px)';
container.style.zIndex = '999999';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.gap = 'clamp(10px, 2vmin, 20px)';
// 统一样式函数
function createButton(text) {
const btn = document.createElement('button');
btn.innerText = text;
// 按钮大小:最小 35px,理想情况屏幕较短边的 8%,最大 60px
const size = 'clamp(35px, 8vmin, 60px)';
btn.style.width = size;
btn.style.height = size;
btn.style.borderRadius = '50%';
btn.style.backgroundColor = 'transparent';
btn.style.color = '#000';
btn.style.border = 'solid #333333';
btn.style.textShadow = `
-1px -1px 0 #ffffff,
1px -1px 0 #ffffff,
-1px 1px 0 #ffffff,
1px 1px 0 #ffffff,
0px 1px 0 #ffffff,
0px -1px 0 #ffffff,
-1px 0px 0 #ffffff,
1px 0px 0 #ffffff
`;
// 字体大小:最小 14px,首选 3vmin,最大 22px
btn.style.fontSize = 'clamp(14px, 3vmin, 22px)';
// 优化:防止频繁点击时选中文本
btn.style.userSelect = 'none';
btn.style.cursor = 'pointer';
// 关闭移动端触摸时产生的灰色高亮背景(最关键的一步)
btn.style.webkitTapHighlightColor = 'transparent';
return btn;
}
const btnUp = createButton('▲');
const btnDown = createButton('▼');
// === 核心改进逻辑:动态检测并记录当前激活的滚动容器 ===
let activeScrollContainer = window;
// 向上遍历DOM树,寻找具有滚动条的容器
function getScrollContainer(node) {
let current = node;
while (current && current !== document && current !== document.body && current !== document.documentElement) {
// 确保是元素节点
if (current.nodeType === 1) {
const style = window.getComputedStyle(current);
const overflowY = style.overflowY;
const isScrollable = (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay');
// 判断是否具有可滚动属性,并且内容确实超出了容器高度
if (isScrollable && current.scrollHeight > current.clientHeight) {
return current;
}
}
current = current.parentNode;
}
return window;
}
// 监听用户的点击或触摸事件,更新目标滚动区域
function updateActiveContainer(e) {
// 如果点击的是悬浮按钮自身,则不更新
if (container.contains(e.target)) return;
let target = e.target;
// 如果点到了文本节点,取它的父元素
if (target.nodeType !== 1) target = target.parentElement;
activeScrollContainer = getScrollContainer(target);
}
// 使用捕获阶段 (true) 监听鼠标点击与平板触摸,确保能第一时间捕捉焦点
document.addEventListener('mousedown', updateActiveContainer, true);
document.addEventListener('touchstart', updateActiveContainer, true);
// 智能获取要滚动的容器
function getTargetContainer() {
// 1. 如果之前记录的容器依然有效且还在 DOM 树中,直接使用
if (activeScrollContainer && activeScrollContainer !== window && document.contains(activeScrollContainer)) {
return activeScrollContainer;
}
// 2. 智能回退:如果没有主动点击页面(刚刷新网页),尝试获取屏幕中心的元素来推测滚动容器
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const el = document.elementFromPoint(centerX, centerY);
if (el) {
const centerContainer = getScrollContainer(el);
if (centerContainer !== window) {
activeScrollContainer = centerContainer;
return centerContainer;
}
}
// 3. 默认回退使用 window
return window;
}
// 执行滚动的统一下发函数
function doScroll(direction) {
const target = getTargetContainer();
// direction: -1 为向上,1 为向下
const distance = direction * window.innerHeight * 0.85;
if (target === window) {
window.scrollBy({ top: distance, behavior: 'instant' });
} else {
target.scrollBy({ top: distance, behavior: 'instant' });
}
}
// 绑定 Page Up 滚动逻辑 (向上滚动当前屏幕高度的85%)
btnUp.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
doScroll(-1);
});
// 绑定 Page Down 滚动逻辑 (向下滚动当前屏幕高度的85%)
btnDown.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
doScroll(1);
});
// 将按钮添加到页面
container.appendChild(btnUp);
container.appendChild(btnDown);
document.body.appendChild(container);
})();