Smart Context Scroll-to-Top (Advanced)

Adds a smart back-to-top button not only for the page, but for every scrollable container (modals, divs).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Smart Context Scroll-to-Top (Advanced)
// @namespace    http://tampermonkey.net/
// @version      2025.12.29.1.2
// @license      MIT License
// @description  Adds a smart back-to-top button not only for the page, but for every scrollable container (modals, divs).
// @description:en  Adds a smart back-to-top button not only for the page, but for every scrollable container (modals, divs).
// @author       rurzowiutki
// @match        *://*/*
// @icon         https://raw.githubusercontent.com/rurzowiutki/scroll-up-icon/refs/heads/main/arrow-up1.png
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // ================= CONFIGURATION =================
    const SCROLL_THRESHOLD = 300;     // After how many pixels should the button appear
    const BUTTON_SIZE = 40;           // Button size in px
    const BUTTON_OFFSET = 20;         // Margin from the edge
    const Z_INDEX = 2137483647;       // Maximum possible Z-Index
    const IGNORE_TEXT_FIELDS = true;  // If true, the button will not appear inside textareas or inputs
    // =================================================

    // WeakMap to store button references without causing memory leaks
    const buttonMap = new WeakMap();

    // Arrow icon (SVG)
    const arrowIcon = `
        <svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round" style="color: white;">
            <line x1="12" y1="19" x2="12" y2="5"></line>
            <polyline points="5 12 12 5 19 12"></polyline>
        </svg>
    `;

    // Inject CSS styles
    const styleId = 'smart-scroll-btn-style';
    function injectStyles() {
        if (!document.getElementById(styleId)) {
            const style = document.createElement('style');
            style.id = styleId;
            style.textContent = `
                .smart-scroll-top-btn {
                    position: fixed;
                    width: ${BUTTON_SIZE}px;
                    height: ${BUTTON_SIZE}px;
                    background: rgba(0, 0, 0, 0.6);
                    border-radius: 50%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    cursor: pointer;
                    opacity: 0;
                    transform: scale(0.8);
                    transition: opacity 0.3s, transform 0.3s, background 0.3s;
                    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
                    pointer-events: none;
                    z-index: ${Z_INDEX};
                }
                .smart-scroll-top-btn.visible {
                    opacity: 1;
                    transform: scale(1);
                    pointer-events: auto;
                }
                .smart-scroll-top-btn:hover {
                    background: rgba(0, 0, 0, 0.85);
                    transform: scale(1.1);
                }
            `;
            (document.head || document.documentElement).appendChild(style);
        }
    }

    // Helper to create the button
    function getOrCreateButton(scrollTarget) {
        if (buttonMap.has(scrollTarget)) {
            return buttonMap.get(scrollTarget);
        }

        // Ensure styles are injected (safe check)
        injectStyles();

        const btn = document.createElement('div');
        btn.className = 'smart-scroll-top-btn';
        btn.innerHTML = arrowIcon;
        btn.title = "Scroll to Top";

        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            scrollTarget.scrollTo({
                top: 0,
                behavior: 'smooth'
            });
        });

        // Append to body (fallback to documentElement if body not ready yet)
        (document.body || document.documentElement).appendChild(btn);

        buttonMap.set(scrollTarget, btn);
        return btn;
    }

    // Helper to position the button
    function updateButtonPosition(btn, target) {
        const isMainPage = target === document || target === document.documentElement || target === document.body;

        if (isMainPage) {
            btn.style.bottom = `${BUTTON_OFFSET}px`;
            btn.style.right = `${BUTTON_OFFSET}px`;
            btn.style.left = 'auto';
            btn.style.top = 'auto';
        } else {
            const rect = target.getBoundingClientRect();

            // If element is hidden, hide button
            if (rect.width === 0 || rect.height === 0) {
                btn.classList.remove('visible');
                return;
            }

            const scrollbarWidth = target.offsetWidth - target.clientWidth;
            const rightPos = (window.innerWidth - rect.right) + BUTTON_OFFSET + scrollbarWidth;
            const bottomPos = (window.innerHeight - rect.bottom) + BUTTON_OFFSET;

            btn.style.right = `${Math.max(BUTTON_OFFSET, rightPos)}px`;
            btn.style.bottom = `${Math.max(BUTTON_OFFSET, bottomPos)}px`;
        }
    }

    // Main Scroll Handler
    window.addEventListener('scroll', (event) => {
        const target = event.target;

        // Basic validation
        if (!target) return;

        // 1. Text Field Check (Fixed: safe check for tagName)
        if (IGNORE_TEXT_FIELDS) {
            // Document object doesn't have tagName, so we default to empty string
            const tagName = (target.tagName || '').toUpperCase();
            const isContentEditable = target.isContentEditable || (target.getAttribute && target.getAttribute('contenteditable') === 'true');

            if (tagName === 'TEXTAREA' || tagName === 'INPUT' || isContentEditable) {
                return;
            }
        }

        // 2. Identify what is being scrolled
        if (target.nodeType !== 1 && target !== document) return;

        let scrollElement = target;
        let scrollTop = target.scrollTop;

        if (target === document) {
            scrollElement = document.documentElement;
            scrollTop = window.scrollY || document.documentElement.scrollTop;
        }

        // 3. Get Button
        const storageKey = (target === document) ? document.documentElement : target;
        const btn = getOrCreateButton(storageKey);

        // 4. Update Visibility
        if (scrollTop > SCROLL_THRESHOLD) {
            updateButtonPosition(btn, scrollElement);
            btn.classList.add('visible');
        } else {
            btn.classList.remove('visible');
        }

    }, { capture: true, passive: true });

})();