Top and Down Scroll Buttons

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