// ==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(); } })();