Universal Scroll Top/Bottom Arrows

Adds floating up and down arrows to the bottom right of every webpage for quick scrolling. Works in dark/light mode.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Universal Scroll Top/Bottom Arrows
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Adds floating up and down arrows to the bottom right of every webpage for quick scrolling. Works in dark/light mode.
// @author       Snow2122
// @license      MIT
// @match        *://*/*
// @run-at       document-idle
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    const CONFIG = {
        size: '45px',
        color: '#ffffff',
        bgColor: '#222222',
        opacity: '0.8',
        hoverOpacity: '1.0',
        distanceFromRight: '20px',
        distanceFromBottom: '70px', // Moved up to avoid footer ads/bars
        zIndex: '2147483647'
    };

    // --- Create Container ---
    const container = document.createElement('div');
    container.id = 'tm-scroll-btn-container';

    // Apply container styles with !important
    container.style.cssText = `
        position: fixed !important;
        bottom: ${CONFIG.distanceFromBottom} !important;
        right: ${CONFIG.distanceFromRight} !important;
        z-index: ${CONFIG.zIndex} !important;
        display: flex !important;
        flex-direction: column !important;
        gap: 8px !important;
        pointer-events: none !important;
        isolation: isolate !important; /* Fixes stacking context issues */
    `;

    // --- Helper Function to Create Buttons ---
    function createButton(svgIcon, action) {
        const btn = document.createElement('div'); // Changed to div to avoid default button styles
        btn.innerHTML = svgIcon;

        // Robust styling with !important
        btn.style.cssText = `
            width: ${CONFIG.size} !important;
            height: ${CONFIG.size} !important;
            background-color: ${CONFIG.bgColor} !important;
            color: ${CONFIG.color} !important;
            border: 2px solid rgba(255, 255, 255, 0.2) !important; /* Visible border for dark mode */
            border-radius: 50% !important; /* Circle shape looks cleaner */
            cursor: pointer !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            opacity: ${CONFIG.opacity} !important;
            transition: all 0.2s ease !important;
            box-shadow: 0 4px 6px rgba(0,0,0,0.3) !important;
            pointer-events: auto !important; /* Re-enable clicks for buttons */
            user-select: none !important;
            -webkit-user-select: none !important;
        `;

        // Hover Effects (via JS since we are using inline styles)
        btn.onmouseenter = () => {
            btn.style.opacity = CONFIG.hoverOpacity;
            btn.style.transform = 'scale(1.1)';
            btn.style.backgroundColor = '#000000';
            btn.style.borderColor = 'rgba(255, 255, 255, 0.8)';
        };

        btn.onmouseleave = () => {
            btn.style.opacity = CONFIG.opacity;
            btn.style.transform = 'scale(1)';
            btn.style.backgroundColor = CONFIG.bgColor;
            btn.style.borderColor = 'rgba(255, 255, 255, 0.2)';
        };

        // Click Action
        btn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            action();
        };

        return btn;
    }

    // --- Smart Scroll Logic ---
    // Finds the element that is actually scrollable (Window vs Wrapper Div)
    function getScrollableTarget() {
        // 1. Check if the main document is scrollable and not locked
        const bodyStyle = window.getComputedStyle(document.body);
        const htmlStyle = window.getComputedStyle(document.documentElement);

        if (bodyStyle.overflowY !== 'hidden' && htmlStyle.overflowY !== 'hidden' &&
           (document.body.scrollHeight > window.innerHeight || document.documentElement.scrollHeight > window.innerHeight)) {
            return window;
        }

        // 2. If main is locked, find the largest scrollable element on screen
        let largestScrollable = null;
        let maxArea = 0;

        // Efficiently query potential scroll containers (divs, main, article)
        const elements = document.querySelectorAll('div, main, article, section');

        for (let el of elements) {
            const style = window.getComputedStyle(el);
            const isScrollable = (style.overflowY === 'auto' || style.overflowY === 'scroll');

            if (isScrollable && el.scrollHeight > el.clientHeight) {
                const rect = el.getBoundingClientRect();
                const area = rect.width * rect.height;
                // We want the largest visible scroll area
                if (area > maxArea && rect.height > 100) {
                    maxArea = area;
                    largestScrollable = el;
                }
            }
        }

        return largestScrollable || window; // Fallback to window
    }

    const scrollUp = () => {
        const target = getScrollableTarget();
        target.scrollTo({
            top: 0,
            behavior: 'smooth'
        });
    };

    const scrollDown = () => {
        const target = getScrollableTarget();

        // Calculate height based on target type
        let topVal = 0;
        if (target === window) {
            topVal = Math.max(
                document.body.scrollHeight,
                document.documentElement.scrollHeight
            );
        } else {
            topVal = target.scrollHeight;
        }

        target.scrollTo({
            top: topVal,
            behavior: 'smooth'
        });
    };

    // --- Assemble UI ---
    const svgStyle = 'width: 24px; height: 24px; stroke-width: 3;';

    const svgUp = `<svg style="${svgStyle}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>`;
    const svgDown = `<svg style="${svgStyle}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>`;

    const btnUp = createButton(svgUp, scrollUp);
    const btnDown = createButton(svgDown, scrollDown);

    container.appendChild(btnUp);
    container.appendChild(btnDown);

    // --- Inject into DOM ---
    const init = () => {
        // Prefer appending to documentElement (html) to escape body stacking contexts
        const target = document.documentElement || document.body;
        if (target) {
            target.appendChild(container);
        } else {
            setTimeout(init, 500);
        }
    };

    // Run initialization
    init();

})();