Scroll Button

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

Verze ze dne 30. 06. 2026. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

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