仿酷安回到顶部和底部
// ==UserScript==
// @name 仿酷安回到顶部和底部
// @namespace optimized-script
// @version 1.0
// @description 支持智能显示隐藏、平滑滚动
// @author DeepSeek
// @match *
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
"use strict";
const CONFIG = {
APPEAR_DELAY: 3000,
MIN_SWIPE_DISTANCE: 40,
BUTTON_SIZE: "clamp(40px, 8vw, 60px)",
BUTTON_BOTTOM: "5vh",
BUTTON_RIGHT: "45vw",
BUTTON_COLOR: "rgba(250,250,250,0.9)",
SHADOW: "0px 1px 1px rgba(0,0,0,0.4)",
ANIMATION_DURATION: 360,
SCROLL_THRESHOLD: 300,
FONT_SIZE: "4vw",
LINE_HEIGHT: "clamp(40px, 8vw, 60px)",
Z_INDEX: 99999999999999999
};
class ScrollButton {
constructor() {
this.isActive = false;
this.lastScroll = 0;
this.touchStartY = 0;
this.hideTimer = null;
this.button = null;
this.init();
}
init() {
this.createStyles();
this.createButton();
this.setupEventListeners();
this.setupIntersectionObserver();
this.setupMutationObserver();
}
createStyles() {
const style = document.createElement("style");
style.textContent = `
.scroll-button {
position: fixed;
right: ${CONFIG.BUTTON_RIGHT};
bottom: ${CONFIG.BUTTON_BOTTOM};
width: ${CONFIG.BUTTON_SIZE};
height: ${CONFIG.BUTTON_SIZE};
line-height: ${CONFIG.LINE_HEIGHT};
text-align: center;
background: ${CONFIG.BUTTON_COLOR};
border-radius: 100%;
box-shadow: ${CONFIG.SHADOW};
cursor: pointer;
opacity: 0;
transform: translateY(20px);
transition:
opacity 0.3s ease,
transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
pointer-events: none;
z-index: ${CONFIG.Z_INDEX};
font-size: ${CONFIG.FONT_SIZE};
color: #000;
display: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.scroll-button.visible {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
display: block;
}
.scroll-button.pressed {
transform: scale(0.92) translateY(0);
background: rgba(240,240,240,0.96);
}
`;
document.head.appendChild(style);
}
createButton() {
this.button = document.createElement("div");
this.button.className = "scroll-button";
this.button.setAttribute("aria-label", "滚动操作按钮");
this.button.setAttribute("role", "button");
document.body.appendChild(this.button);
}
setupEventListeners() {
const passiveOptions = { passive: true };
const activeOptions = { capture: true };
document.addEventListener("touchstart", this.handleTouchStart.bind(this), passiveOptions);
document.addEventListener("touchmove", this.throttle(this.handleTouchMove.bind(this), 50), passiveOptions);
document.addEventListener("touchend", this.handleTouchEnd.bind(this), passiveOptions);
document.addEventListener("mousedown", this.handleMouseStart.bind(this), activeOptions);
document.addEventListener("mousemove", this.throttle(this.handleMouseMove.bind(this), 50), activeOptions);
document.addEventListener("mouseup", this.handleMouseEnd.bind(this), activeOptions);
this.button.addEventListener("click", this.handleClick.bind(this));
this.button.addEventListener("transitionend", () => {
this.button.classList.remove("pressed");
});
}
setupIntersectionObserver() {
let ticking = false;
let lastScroll = window.scrollY;
const handleScroll = () => {
const currentScroll = window.scrollY;
const scrollDirection = currentScroll > lastScroll ? "down" : "up";
const scrollDelta = Math.abs(currentScroll - lastScroll);
if (scrollDelta > CONFIG.SCROLL_THRESHOLD) {
this.showButton(scrollDirection);
}
lastScroll = currentScroll;
ticking = false;
};
window.addEventListener("scroll", () => {
if (!ticking) {
requestAnimationFrame(handleScroll);
ticking = true;
}
}, { passive: true });
}
setupMutationObserver() {
const observer = new MutationObserver(() => {
if (!document.querySelector(".scroll-button")) {
this.createButton();
this.setupEventListeners();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
throttle(fn, delay) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
fn.apply(this, args);
lastCall = now;
}
};
}
handleTouchStart(e) {
this.touchStartY = e.touches[0].clientY;
this.isActive = true;
}
handleTouchMove(e) {
if (!this.isActive) return;
this.updateButtonState(e.touches[0].clientY);
}
handleTouchEnd() {
this.isActive = false;
this.scheduleHide();
}
handleMouseStart(e) {
this.touchStartY = e.clientY;
this.isActive = true;
}
handleMouseMove(e) {
if (!this.isActive) return;
this.updateButtonState(e.clientY);
}
handleMouseEnd() {
this.isActive = false;
this.scheduleHide();
}
updateButtonState(currentY) {
const deltaY = currentY - this.touchStartY;
if (Math.abs(deltaY) < CONFIG.MIN_SWIPE_DISTANCE) return;
const direction = deltaY > 0 ? "up" : "down";
this.showButton(direction);
}
showButton(direction) {
clearTimeout(this.hideTimer);
this.button.textContent = direction === "up" ? "↑" : "↓";
requestAnimationFrame(() => {
this.button.classList.add("visible");
this.button.style.transitionDuration = `${CONFIG.ANIMATION_DURATION}ms`;
});
}
scheduleHide() {
clearTimeout(this.hideTimer);
this.hideTimer = setTimeout(() => {
this.button.classList.remove("visible");
}, CONFIG.APPEAR_DELAY);
}
handleClick() {
const targetPosition = this.button.textContent === "↑"
? 0
: document.documentElement.scrollHeight;
window.scrollTo({
top: targetPosition,
behavior: "smooth"
});
this.button.classList.add("pressed");
this.scheduleHide();
}
}
if (!document.querySelector(".scroll-button")) {
new ScrollButton();
}
})();