Scroll Button

Add buttons to scroll to the bottom and top of the website

Från och med 2026-06-30. Se den senaste versionen.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         Scroll Button
// @name:zh      滚动按钮
// @namespace    https://greasyfork.org/
// @version      1.2
// @description  Add buttons to scroll to the bottom and top of the website
// @description:zh 添加按钮以滚动到网页的底部和顶部
// @author       chowxi
// @match        *://*/*
// @license      MIT
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    if (window !== window.top) {
        return;
    }

    function throttle(func, delay) {
        let lastCall = 0;
        return function(...args) {
            const now = Date.now();
            if (now - lastCall < delay) {
                return;
            }
            lastCall = now;
            return func.apply(this, args);
        };
    }

    function createButton(iconBase64, altText, buttonStyles) {
        const btn = document.createElement('button');
        const img = document.createElement('img');
        img.src = `data:image/svg+xml;base64,${iconBase64}`;
        img.alt = altText;
        img.style.width = '16px';
        img.style.height = '16px';
        img.style.display = 'block';
        btn.appendChild(img);
        Object.assign(btn.style, buttonStyles);
        return btn;
    }

    function setHoverShadow(button) {
        button.addEventListener('mouseenter', function() {
            button.style.boxShadow = '0 4px 8px rgba(0,0,0,0.25)';
        });
        button.addEventListener('mouseleave', function() {
            button.style.boxShadow = '0 1px 3px rgba(0,0,0,0.15)';
        });
    }

    function init() {
        if (!document.body) {
            // 如果 document.body 不存在,等待 DOMContentLoaded 事件
            window.addEventListener('DOMContentLoaded', init);
            return;
        }

        // Base64-encoded SVG data for bottom icon
        const base64BottomIcon =
            'PHN2ZyBzdHJva2U9ImN1cnJlbnRDb2xvciIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9Imljb24tc20gbS0xIiBoZWlnaHQ9IjFlbSIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48bGluZSB4MT0iMTIiIHkxPSI1IiB4Mj0iMTIiIHkyPSIxOSI+PC9saW5lPjxwb2x5bGluZSBwb2ludHM9IjE5IDEyIDEyIDE5IDUgMTIiPjwvcG9seWxpbmU+PC9zdmc+';

        // Base64-encoded SVG data for top icon
        const base64TopIcon =
            'PHN2ZyBzdHJva2U9ImN1cnJlbnRDb2xvciIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9Imljb24tc20gbS0xIiBoZWlnaHQ9IjFlbSIgd2lkdGg9IjFlbSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48bGluZSB4MT0iMTIiIHkxPSIxOSIgeDI9IjEyIiB5Mj0iNSI+PC9saW5lPjxwb2x5bGluZSBwb2ludHM9IjUgMTIgMTIgNSAxOSAxMiI+PC9wb2x5bGluZT48L3N2Zz4=';

        // Common styles for both buttons
        const buttonStyles = {
            position: 'fixed',
            zIndex: '99',
            width: '25px',
            height: '25px',
            minWidth: '25px',
            minHeight: '25px',
            maxWidth: '25px',
            maxHeight: '25px',
            flexShrink: '0',
            boxSizing: 'border-box',
            backgroundColor: 'transparent',
            backdropFilter: 'blur(8px)',
            WebkitBackdropFilter: 'blur(8px)',
            boxShadow: '0 1px 3px rgba(0,0,0,0.35)',
            border: '0.5px solid transparent',
            borderRadius: '50%',
            padding: '4px',
            margin: '0',
            cursor: 'pointer',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            transition: 'opacity 0.2s, box-shadow 0.2s'
        };

        const bottomButton = createButton(base64BottomIcon, 'Scroll to Bottom', buttonStyles);
        bottomButton.style.bottom = '14px';
        bottomButton.style.right = '14px';

        bottomButton.addEventListener('click', function() {
            window.scrollTo({
                top: document.documentElement.scrollHeight || document.body.scrollHeight,
                behavior: 'smooth'
            });
        });

        const topButton = createButton(base64TopIcon, 'Scroll to Top', buttonStyles);
        topButton.style.bottom = '50px';
        topButton.style.right = '14px';

        topButton.addEventListener('click', function() {
            window.scrollTo({
                top: 0,
                behavior: 'smooth'
            });
        });

        document.body.append(bottomButton, topButton);

        setHoverShadow(bottomButton);
        setHoverShadow(topButton);

        // 智能避让:检测右下角是否有其他浮动元素,自动上移按钮
        function avoidOverlap() {
            const margin = 14;        // 按钮离右边的基础距离
            const baseBottom = 14;    // 最底部按钮的基础距离
            const gap = 36;           // 按钮之间的固定间距
            const buttonSize = 32;    // 按钮估算尺寸(含 padding)

            let extraOffset = 0;

            const allElements = document.querySelectorAll('body *');
            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;

            allElements.forEach(el => {
                if (el === bottomButton || el === topButton || bottomButton.contains(el) || topButton.contains(el)) {
                    return;
                }

                const style = window.getComputedStyle(el);
                if (style.position !== 'fixed' && style.position !== 'sticky') {
                    return;
                }
                if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) {
                    return;
                }

                const rect = el.getBoundingClientRect();
                if (rect.width === 0 || rect.height === 0) {
                    return;
                }

                const isInBottomRightZone =
                    rect.right > viewportWidth - 120 &&
                    rect.bottom > viewportHeight - 150;

                if (isInBottomRightZone) {
                    const occupiedFromBottom = viewportHeight - rect.top;
                    if (occupiedFromBottom > extraOffset) {
                        extraOffset = occupiedFromBottom;
                    }
                }
            });

            const offset = extraOffset > 0 ? extraOffset + 8 : 0;

            bottomButton.style.setProperty('bottom', `${baseBottom + offset}px`, 'important');
            topButton.style.setProperty('bottom', `${baseBottom + gap + offset}px`, 'important');
        }

        avoidOverlap();

        window.addEventListener('scroll', throttle(avoidOverlap, 300));
        window.addEventListener('resize', throttle(avoidOverlap, 300));

        const observer = new MutationObserver(throttle(avoidOverlap, 500));
        observer.observe(document.body, { childList: true, subtree: true });

        function toggleTopButton() {
            topButton.style.display = window.scrollY === 0 ? 'none' : 'flex';
        }

        toggleTopButton();
        window.addEventListener('scroll', throttle(toggleTopButton, 100));
    }

    init();
})();