// ==UserScript==
// @name 网页随心滚
// @namespace http://tampermonkey.net/
// @version 2.0
// @description 滚动到顶/底、滚动条-新、记录页面滚动、屏幕常亮和自动滚动
// @author ^o^
// @match *://*/*
// @grant GM_registerMenuCommand
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
let w = window, d = document;
let eScrollBtn = true, eScrollBar = true, eWakeLock = true;
let scrollBarPosition = localStorage.getItem('scrollBarPosition') || 'right'; // 'left' or 'right'
let scrollBtnPosition = localStorage.getItem('scrollBtnPosition') || 'right'; // 'left' or 'right'
// 注册菜单命令
GM_registerMenuCommand('滚动到顶/底按钮:开/关', toggleScrollBtn);
GM_registerMenuCommand('滚动条:开/关', toggleScrollBar);
GM_registerMenuCommand('滚动条位置:左/右切换', toggleScrollBarPosition);
GM_registerMenuCommand('滚动到顶/底按钮位置:左/右切换', toggleScrollBtnPosition);
GM_registerMenuCommand('保持屏幕常亮:开/关', toggleWakeLock);
function toggleScrollBtn() {
eScrollBtn = !eScrollBtn;
let b = d.getElementById('scroll-top-btn');
if (b) b.remove();
if (eScrollBtn) initScrollBtn();
}
function toggleScrollBar() {
eScrollBar = !eScrollBar;
let s = d.getElementById('theScrollBar');
if (s) s.remove();
if (eScrollBar) initScrollBar();
}
function toggleWakeLock() {
eWakeLock = !eWakeLock;
if (eWakeLock) requestWL();
else if (wakeLock) wakeLock.release();
}
function toggleScrollBarPosition() {
scrollBarPosition = scrollBarPosition === 'left' ? 'right' : 'left';
localStorage.setItem('scrollBarPosition', scrollBarPosition);
let s = d.getElementById('theScrollBar');
if (s) {
if (scrollBarPosition === 'right') {
s.style.right = '2vw';
s.style.left = 'auto';
} else {
s.style.left = '2vw';
s.style.right = 'auto';
}
}
}
function toggleScrollBtnPosition() {
scrollBtnPosition = scrollBtnPosition === 'left' ? 'right' : 'left';
localStorage.setItem('scrollBtnPosition', scrollBtnPosition);
let b = d.getElementById('scroll-top-btn');
if (b) {
b.style.left = scrollBtnPosition === 'left' ? '15px' : '';
b.style.right = scrollBtnPosition === 'right' ? '15px' : '';
}
}
function initScrollBtn() {
let b = d.createElement('button');
b.textContent = '▲';
b.id = 'scroll-top-btn';
Object.assign(b.style, {
position: 'fixed', bottom: '15%', zIndex: 999999,
width: '35px', height: '35px', borderRadius: '50%', padding: 0,
background: 'rgba(255,255,255,0.3)', backdropFilter: 'blur(5px)',
display: 'none',
fontSize: '16px', textAlign: 'center', lineHeight: '35px',
fontWeight: 'bold', cursor: 'pointer', transition: 'all 0.3s ease',
border: 'none'
});
// 根据位置设置 left 或 right
if (scrollBtnPosition === 'left') {
b.style.left = '13px';
} else {
b.style.right = '13px';
}
d.body.appendChild(b);
let lastY = w.pageYOffset, t;
w.addEventListener('scroll', () => {
b.textContent = w.scrollY > lastY ? '▼' : '▲';
lastY = w.scrollY;
b.style.display = w.pageYOffset > 100 ? 'block' : 'none';
clearTimeout(t);
t = setTimeout(() => b.style.display = 'none', 2000);
});
b.addEventListener('mouseenter', () => {
b.style.background = 'rgba(255,255,255,0.6)';
b.style.transform = 'scale(1.1)';
});
b.addEventListener('mouseleave', () => {
b.style.background = 'rgba(255,255,255,0.3)';
b.style.transform = 'scale(1)';
});
b.addEventListener('click', () => w.scrollTo({
top: b.textContent === '▲' ? 0 : d.documentElement.scrollHeight,
behavior: 'smooth'
}));
}
function initScrollBar() {
// 创建滚动条 DOM 元素
const theScrollBar = document.createElement("div");
// 设置滚动条的 ID 和文本
theScrollBar.id = "theScrollBar";
theScrollBar.innerHTML = "▲<br>▼";
// 设置滚动条样式
theScrollBar.setAttribute(
"style",
"font-size:2.6vw ;width:6vw ;line-height:5vw ;display: block;text-align:center ;background-color:rgba(255,255,255) ;opacity: 0 ;box-shadow:0px 1px 5px rgba(0,0,0,0.2) ;color:#000 ;position:fixed ;top: -14vw;right:-10vw ;z-index:9999999 ;transition: opacity 0.4s ease-in-out,right 0.4s; border-radius:3vw "
);
// 根据位置设置 left 或 right
if (scrollBarPosition === 'left') {
theScrollBar.style.left = '2vw';
theScrollBar.style.right = 'auto';
} else {
theScrollBar.style.right = '-10vw';
theScrollBar.style.left = 'auto';
}
// 将滚动条元素添加到页面中
document.body.appendChild(theScrollBar);
// 存储网页高度和上次滚动位置
let webHeight = null;
let lastScrollTop = null;
// 更新滚动条位置的函数
function updateScrollBar() {
// 获取当前的滚动位置
const scrollTop = window.scrollY;
// 如果滚动位置改变了,重新计算滚动条位置
if (scrollTop !== lastScrollTop) {
const scrollBarTop =
(scrollTop / webHeight) * (window.innerHeight - theScrollBar.clientHeight);
if (scrollBarTop < 0) {
theScrollBar.style.top = "0";
} else if (scrollBarTop + theScrollBar.clientHeight > window.innerHeight) {
theScrollBar.style.top = `${window.innerHeight - theScrollBar.clientHeight}px`;
webHeight = document.documentElement.scrollHeight - window.innerHeight;
} else {
theScrollBar.style.top = `${scrollBarTop}px`;
}
lastScrollTop = scrollTop;
}
// 使用 requestAnimationFrame 函数,优化渲染性能
window.requestAnimationFrame(updateScrollBar);
}
// 添加 touchstart 事件监听器,当用户在手机上开始触摸屏幕时触发
window.addEventListener("touchstart", function() {
//如果网页高度过低,不需要添加滚动条
if (document.documentElement.scrollHeight <= window.innerHeight * 2) {
return;
}
// 获取网页高度,并开始更新滚动条位置
webHeight = document.documentElement.scrollHeight - window.innerHeight;
updateScrollBar();
});
// 定义一些与触摸事件相关的变量
let startOffset = null;
// 滚动条开始滚动时触发的函数
function startScroll(event) {
event.preventDefault();
event.stopPropagation();
startOffset = event.changedTouches[0].clientY - parseInt(theScrollBar.style.top);
}
// 滚动条正在滚动时触发的函数
function scrolling(event) {
event.preventDefault();
event.stopPropagation();
// 计算当前滚动条的位置和滑动距离,并更新滚动位置和滚动条位置
const currentY = event.changedTouches[0].clientY;
const scrollBarTop = currentY - startOffset;
if (scrollBarTop < 0) {
theScrollBar.style.top = "0px";
} else if (scrollBarTop > window.innerHeight - theScrollBar.clientHeight) {
theScrollBar.style.top = `${window.innerHeight - theScrollBar.clientHeight}px`;
} else {
theScrollBar.style.top = `${scrollBarTop}px`;
}
const scrollTop =
(scrollBarTop / (window.innerHeight - theScrollBar.clientHeight)) * webHeight;
window.scrollTo(window.scrollX, scrollTop);
}
// 为滚动条添加触摸事件监听器
theScrollBar.addEventListener("touchstart", startScroll, { passive: false });
theScrollBar.addEventListener("touchmove", scrolling, { passive: false });
// 停止滚动1秒后隐藏
let timer;
window.addEventListener("scroll", function() {
clearTimeout(timer);
if (scrollBarPosition === 'right') {
theScrollBar.style.right = "2vw";
theScrollBar.style.left = "auto";
} else {
theScrollBar.style.left = "2vw";
theScrollBar.style.right = "auto";
}
theScrollBar.style.opacity = "0.8";
timer = setTimeout(() => {
if (scrollBarPosition === 'right') {
theScrollBar.style.right = "-10vw";
theScrollBar.style.left = "auto";
} else {
theScrollBar.style.left = "-10vw";
theScrollBar.style.right = "auto";
}
theScrollBar.style.opacity = "0";
}, 1000);
});
// 定义触顶/底反弹动画的函数
function bounceAnimation() {
// 获取当前滚动条的位置
const scrollTop = window.scrollY;
// 获取滚动条的高度
const scrollBarHeight = theScrollBar.clientHeight;
// 获取滚动速度
const scrollSpeed = Math.abs(scrollTop - lastScrollTop);
// 设置滚动速度阈值
const threshold = 7;
// 如果滚动条触顶,且滚动速度超过阈值,执行反弹动画
if (scrollTop === 0 && scrollSpeed > threshold) {
theScrollBar.style.animation = "bounce-down 0.4s";
setTimeout(() => {
theScrollBar.style.animation = "";
}, 500);
}
// 如果滚动条触底,且滚动速度超过阈值,执行反弹动画
if (scrollTop + window.innerHeight >= document.documentElement.scrollHeight && scrollSpeed > threshold) {
theScrollBar.style.animation = "bounce-up 0.4s";
setTimeout(() => {
theScrollBar.style.animation = "";
}, 500);
}
}
// 添加触顶/底反弹动画的事件监听器
window.addEventListener("scroll", bounceAnimation);
// CSS样式
const styles = `
@keyframes bounce-down {
0% {
transform: translateY(0);
}
30% {
transform: translateY(10px);
}
100% {
transform: translateY(0);
}
}
@keyframes bounce-up {
0% {
transform: translateY(0);
}
30% {
transform: translateY(-10px);
}
100% {
transform: translateY(0);
}
}
`;
// 创建样式元素并将样式添加到头部
const styleElement = document.createElement("style");
styleElement.innerHTML = styles;
document.head.appendChild(styleElement);
}
let wakeLock = null;
async function requestWL() {
if (!('wakeLock' in navigator)) return;
try {
wakeLock = await navigator.wakeLock.request("screen");
} catch (e) {}
}
eWakeLock && requestWL();
let scrolling = false, interval, speed = 25, pixels = 1;
function toggleAutoScroll() {
if (scrolling) {
scrolling = false;
clearInterval(interval);
} else {
scrolling = true;
interval = setInterval(() => {
w.scrollBy(0, pixels);
if (w.innerHeight + w.scrollY >= d.body.scrollHeight) {
w.scrollBy(0, 1);
}
}, speed);
}
}
GM_registerMenuCommand('自动滚动:开始/停止', toggleAutoScroll);
GM_registerMenuCommand('自动滚动:配置参数', () => {
let s = prompt('滚动间隔(ms):', speed);
if (s !== null) speed = parseInt(s) || speed;
let p = prompt('每次滚动像素(px):', pixels);
if (p !== null) pixels = parseInt(p) || pixels;
});
eScrollBtn && initScrollBtn();
eScrollBar && initScrollBar();
// 记录页面滚动位置
w.addEventListener('scroll', () => {
localStorage.setItem('pageScrollPosition', JSON.stringify({
top: w.scrollY,
left: w.scrollX
}));
});
// 页面加载时恢复滚动位置
w.addEventListener('load', () => {
let position = localStorage.getItem('pageScrollPosition');
if (position) {
let scrollPosition = JSON.parse(position);
w.scrollTo(scrollPosition.left, scrollPosition.top);
}
});
})();