Top and Down Scroll Buttons

Smooth scroll buttons for top and bottom of page

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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