Top and Down Scroll Buttons

Smooth scroll buttons for top and bottom of page

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

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