您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Smooth scroll buttons for top and bottom of page
// ==UserScript== // @name Top and Down Scroll Buttons // @description Smooth scroll buttons for top and bottom of page // @version 1.1 // @author lunagus // @license MIT // @include * // @run-at document-end // @grant none // @namespace https://greasyfork.org/users/1517518 // ==/UserScript== (function() { 'use strict'; // Skip iframes if (window.self !== window.top) return; // Configuration const CONFIG = { speedClick: 500, // Smooth scroll duration on click (ms) speedHover: 100, // Continuous scroll speed on hover (ms) zIndex: 1001, // Z-index for buttons scrollStep: 1, // Pixels to scroll per interval on hover hideThreshold: 0 // Scroll position to show/hide buttons }; // SVG icons (inline for better performance) const ICONS = { up: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"%3E%3Cpath d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/%3E%3C/svg%3E', down: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"%3E%3Cpath d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/%3E%3C/svg%3E' }; // Inject CSS const injectStyles = () => { const style = document.createElement('style'); style.textContent = ` .scroll-btn { position: fixed; right: 0; width: 40px; height: 40px; background: rgba(0, 0, 0, 0.7); border-radius: 5px 0 0 5px; cursor: pointer; opacity: 0.65; transition: opacity 0.3s ease, transform 0.2s ease; z-index: ${CONFIG.zIndex}; display: flex; align-items: center; justify-content: center; user-select: none; } .scroll-btn:hover { opacity: 1; transform: translateX(-2px); } .scroll-btn:active { transform: translateX(-2px) scale(0.95); } .scroll-btn-up { bottom: calc(50% + 25px); } .scroll-btn-down { top: calc(50% + 25px); } .scroll-btn-hidden { display: none; } .scroll-btn img { width: 24px; height: 24px; pointer-events: none; } `; document.head.appendChild(style); }; // Easing function for smooth scroll const easeInOutQuad = (t, b, c, d) => { t /= d / 2; if (t < 1) return c / 2 * t * t + b; t--; return -c / 2 * (t * (t - 2) - 1) + b; }; // Smooth scroll to position const smoothScrollTo = (target, duration = CONFIG.speedClick) => { const start = window.pageYOffset || document.documentElement.scrollTop; const change = target - start; const increment = 20; let currentTime = 0; const animateScroll = () => { currentTime += increment; const val = easeInOutQuad(currentTime, start, change, duration); window.scrollTo(0, val); if (currentTime < duration) { requestAnimationFrame(animateScroll); } }; animateScroll(); }; // Scroll animation class class ScrollAnimator { constructor() { this.animationFrame = null; this.direction = 0; // 0: stop, 1: up, -1: down } start(direction) { this.direction = direction; if (!this.animationFrame) { this.animate(); } } stop() { this.direction = 0; if (this.animationFrame) { cancelAnimationFrame(this.animationFrame); this.animationFrame = null; } } animate() { if (this.direction === 0) { this.animationFrame = null; return; } const currentPos = window.pageYOffset || document.documentElement.scrollTop; window.scrollTo(0, currentPos - this.direction * CONFIG.scrollStep); this.animationFrame = requestAnimationFrame(() => this.animate()); } } // Button manager class ScrollButtons { constructor() { this.animator = new ScrollAnimator(); this.upBtn = null; this.downBtn = null; this.init(); } createButton(className, iconData) { const btn = document.createElement('div'); btn.className = `scroll-btn ${className}`; const img = document.createElement('img'); img.src = iconData; img.alt = className.includes('up') ? 'Scroll to top' : 'Scroll to bottom'; btn.appendChild(img); return btn; } getDocumentHeight() { return Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight ); } updateButtonVisibility() { const scrolled = window.pageYOffset || document.documentElement.scrollTop; const maxScroll = this.getDocumentHeight() - window.innerHeight; // Show/hide up button this.upBtn.classList.toggle('scroll-btn-hidden', scrolled <= CONFIG.hideThreshold); // Show/hide down button this.downBtn.classList.toggle('scroll-btn-hidden', scrolled >= maxScroll - 10); } init() { // Check if page is scrollable const isScrollable = this.getDocumentHeight() > window.innerHeight; if (!isScrollable) return; // Inject styles injectStyles(); // Create buttons this.upBtn = this.createButton('scroll-btn-up', ICONS.up); this.downBtn = this.createButton('scroll-btn-down', ICONS.down); // Add event listeners for up button this.upBtn.addEventListener('mouseenter', () => this.animator.start(1)); this.upBtn.addEventListener('mouseleave', () => this.animator.stop()); this.upBtn.addEventListener('click', () => { this.animator.stop(); smoothScrollTo(0); }); // Add event listeners for down button this.downBtn.addEventListener('mouseenter', () => this.animator.start(-1)); this.downBtn.addEventListener('mouseleave', () => this.animator.stop()); this.downBtn.addEventListener('click', () => { this.animator.stop(); smoothScrollTo(this.getDocumentHeight()); }); // Append to body document.body.appendChild(this.upBtn); document.body.appendChild(this.downBtn); // Initial visibility update this.updateButtonVisibility(); // Update on scroll (throttled) let scrollTimeout; window.addEventListener('scroll', () => { if (scrollTimeout) return; scrollTimeout = setTimeout(() => { this.updateButtonVisibility(); scrollTimeout = null; }, 100); }, { passive: true }); // Update on resize window.addEventListener('resize', () => { this.updateButtonVisibility(); }, { passive: true }); } } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => new ScrollButtons()); } else { new ScrollButtons(); } })();