Greasy Fork is available in English.
在右下角增添可以置顶或置底的按钮,长按可拖动
// ==UserScript==
// @name 页面导航
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description 在右下角增添可以置顶或置底的按钮,长按可拖动
// @author G31415
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
const id = 'g' + Math.random().toString(36).slice(2,7);
const btn = Object.assign(document.createElement('button'), {
id,
textContent: '▼',
title: '点击跳转'
});
// 状态变量
let isDragging = false, dragActive = false, pressTimer = null;
let startX, startY, startRight, startBottom;
let lastY = window.scrollY;
const style = document.createElement('style');
style.textContent = `
#${id} {
all: initial;
position: fixed;
bottom: 0;
right: 0;
width: 32px;
height: 32px;
font: 16px/32px sans-serif;
text-align: center;
color: #000;
background: transparent;
border: none;
cursor: pointer;
z-index: 9999;
user-select: none;
-webkit-tap-highlight-color: transparent;
outline: none; /* 取消蓝色方框 */
display: none;
touch-action: none; /* 核心:防止移动端手势干扰拖拽 */
}
#${id}.visible { display: block; }
#${id}:hover { transform: scale(1); }
#${id}:active { transform: scale(0.82); }
#${id}.dragging {
opacity: 0.6;
cursor: grabbing;
transform: scale(1);
transition: none;
}
@media (max-width: 768px) {
#${id} { width: 44px; height: 44px; font-size: 26px; line-height: 44px; }
}
`;
document.head.appendChild(style);
document.body.appendChild(btn);
// 拖拽逻辑更新为 Pointer 事件(支持鼠标和触摸)
const handlePointerMove = (e) => {
if (!dragActive) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
// 计算新位置并限制在视口内
let newRight = Math.max(0, Math.min(window.innerWidth - btn.offsetWidth, startRight - deltaX));
let newBottom = Math.max(0, Math.min(window.innerHeight - btn.offsetHeight, startBottom - deltaY));
btn.style.right = newRight + 'px';
btn.style.bottom = newBottom + 'px';
};
btn.onpointerdown = (e) => {
if (e.button !== 0 && e.pointerType === 'mouse') return;
pressTimer = setTimeout(() => {
dragActive = isDragging = true;
btn.classList.add('dragging');
// 锁定指针,拖拽更稳
btn.setPointerCapture(e.pointerId);
startX = e.clientX;
startY = e.clientY;
const s = getComputedStyle(btn);
startRight = parseFloat(s.right) || 0;
startBottom = parseFloat(s.bottom) || 0;
}, 500);
};
btn.onpointerup = (e) => {
clearTimeout(pressTimer);
if (dragActive) {
dragActive = isDragging = false;
btn.classList.remove('dragging');
btn.releasePointerCapture(e.pointerId);
} else if (pressTimer && btn.classList.contains('visible')) {
// 短按触发跳转
const isToTop = btn.textContent === '▲';
window.scrollTo(0, isToTop ? 0 : document.documentElement.scrollHeight);
}
pressTimer = null;
};
// 全局移动监听
window.addEventListener('pointermove', handlePointerMove);
// 滚动监听(增加性能节流)
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
const y = window.scrollY;
if (y > 0) btn.classList.add('visible');
if (btn.classList.contains('visible') && Math.abs(y - lastY) > 5) {
const isDown = y > lastY;
btn.textContent = isDown ? '▼' : '▲';
btn.title = isDown ? '点击跳转到底部' : '点击跳转到顶部';
}
lastY = y;
ticking = false;
});
ticking = true;
}
}, { passive: true });
// 守护按钮不被意外删除
new MutationObserver(muts => muts.forEach(m =>
m.removedNodes.forEach(n => n.id === id && document.body.appendChild(btn))
)).observe(document.body, { childList: true });
})();