scroll up and down buttons

Add up/down navigation buttons to scroll to top or bottom of the page, hide during fullscreen, plus small toggle to make buttons transparent

当前为 2025-09-22 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         scroll up and down buttons
// @namespace    http://tampermonkey.net/
// @version      1.3.1
// @description  Add up/down navigation buttons to scroll to top or bottom of the page, hide during fullscreen, plus small toggle to make buttons transparent
// @author       You
// @match        *://*/*
// @grant        none
// @license Apache 2.0
// ==/UserScript==

(function () {
    'use strict';

    if (window.top !== window.self) return;
    if (document.body && document.body.children.length === 1) {
        const onlyChild = document.body.children[0];
        const tag = onlyChild.tagName.toLowerCase();
        if (['img', 'video', 'audio', 'embed', 'object'].includes(tag)) return;
    }

    let scrollAnimationId = null;

    function smoothScrollTo(targetY) {
        if (scrollAnimationId) cancelAnimationFrame(scrollAnimationId);

        const startY = window.scrollY;
        const distance = targetY - startY;
        const duration = 1000;
        const startTime = performance.now();

        function step(currentTime) {
            const elapsed = currentTime - startTime;
            const progress = Math.min(elapsed / duration, 1);
            const ease = progress < 0.5
                ? 2 * progress * progress
                : -1 + (4 - 2 * progress) * progress;

            window.scrollTo(0, startY + distance * ease);

            if (progress < 1) {
                scrollAnimationId = requestAnimationFrame(step);
            } else {
                scrollAnimationId = null;
            }
        }

        scrollAnimationId = requestAnimationFrame(step);
    }

    function stopScroll() {
        if (scrollAnimationId) {
            cancelAnimationFrame(scrollAnimationId);
            scrollAnimationId = null;
        }
    }

    ['mousedown', 'wheel', 'touchstart', 'keydown'].forEach(evt => {
        window.addEventListener(evt, stopScroll, { passive: true });
    });

    // --- Styles (use classes so toggling is easy) ---
    const style = document.createElement('style');
    style.textContent = `
        .tm-scroll-container { position: fixed; bottom: 10px; right: 10px; display: flex; flex-direction: column; gap: 6px; z-index: 9999999; pointer-events: auto; align-items: center; }
        .tm-scroll-btn { background-color: #333; color: white; font-size: 17px; padding: 8px 12px; border: none; border-radius: 6px; cursor: pointer; box-shadow: 0 2px 6px rgba(0,0,0,0.3); transition: background-color 0.2s, opacity 0.2s; }
        .tm-toggle-btn { width: 28px; height: 28px; padding: 0; border-radius: 50%; font-size: 14px; display:flex; align-items:center; justify-content:center; background-color: #555; color: #fff; border:none; cursor:pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.3); }
        .tm-scroll-btn.transparent { opacity: 0; }
        .tm-scroll-btn.transparent { opacity: 0.1; }
        .tm-toggle-btn.active { background-color: #1a73e8; color: #fff; }
    `;
    document.head.appendChild(style);

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

    // Toggle button (small, visible)
    const toggleBtn = document.createElement('button');
    toggleBtn.className = 'tm-toggle-btn';
    toggleBtn.title = 'Toggle button transparency';
    toggleBtn.setAttribute('aria-pressed', 'false');
    toggleBtn.textContent = '◐'; // icon-like visual
    container.appendChild(toggleBtn);

    // Up button
    const upBtn = document.createElement('button');
    upBtn.className = 'tm-scroll-btn';
    upBtn.textContent = '▲';
    upBtn.title = 'Scroll to top';
    upBtn.onclick = () => smoothScrollTo(0);

    // Down button
    const downBtn = document.createElement('button');
    downBtn.className = 'tm-scroll-btn';
    downBtn.textContent = '▼';
    downBtn.title = 'Scroll to bottom';
    downBtn.onclick = () => smoothScrollTo(document.documentElement.scrollHeight || document.body.scrollHeight);

    container.appendChild(upBtn);
    container.appendChild(downBtn);
    document.body.appendChild(container);

    // Persisted state key
    const STORAGE_KEY = 'tm_scroll_transparent_v1';

    // Apply saved state (if any)
    function applyTransparencyState(state) {
        if (state) {
            upBtn.classList.add('transparent');
            downBtn.classList.add('transparent');
            toggleBtn.classList.add('active');
            toggleBtn.setAttribute('aria-pressed', 'true');
            toggleBtn.textContent = '◑';
        } else {
            upBtn.classList.remove('transparent');
            downBtn.classList.remove('transparent');
            toggleBtn.classList.remove('active');
            toggleBtn.setAttribute('aria-pressed', 'false');
            toggleBtn.textContent = '◐';
        }
        try {
            localStorage.setItem(STORAGE_KEY, state ? '1' : '0');
        } catch (e) { /* ignore storage errors */ }
    }

    // Initialize from storage
    let saved = null;
    try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) { saved = null; }
    applyTransparencyState(saved === '1');

    // Toggle handler
    toggleBtn.addEventListener('click', (e) => {
        const enabled = upBtn.classList.contains('transparent');
        applyTransparencyState(!enabled);
        // keep the small toggle visible (we only change the two main buttons)
        e.stopPropagation();
    });

    // Hide during fullscreen
    document.addEventListener('fullscreenchange', () => {
        container.style.display = document.fullscreenElement ? 'none' : 'flex';
    });

    // Ensure container is removed/hidden on pages that replace body later (basic guard)
    const observer = new MutationObserver(() => {
        if (!document.body || document.body.contains(container) === false) {
            if (document.body) document.body.appendChild(container);
        }
    });
    observer.observe(document.documentElement || document, { childList: true, subtree: true });

})();