scroll-button

scroll-button (Mobile Optimized: Smaller & Auto-Fade)

À partir de 2025-12-17. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         scroll-button
// @namespace    https://github.com/livinginpurple
// @version      20251217.03
// @description  scroll-button (Mobile Optimized: Smaller & Auto-Fade)
// @license      WTFPL
// @author       livinginpurple
// @include      *
// @run-at       document-end
// @grant        none
// ==/UserScript==

(() => {
    'use strict';

    const init = () => {
        const btnId = 'gamma-scroll-btn';
        if (document.getElementById(btnId)) return;

        // SVG 圖示 (保持不變)
        const icons = {
            up: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="white" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 12a.5.5 0 0 0 .5-.5V5.707l2.146 2.147a.5.5 0 0 0 .708-.708l-3-3a.5.5 0 0 0-.708 0l-3 3a.5.5 0 1 0 .708.708L7.5 5.707V11.5a.5.5 0 0 0 .5.5z"/></svg>`,
            down: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="white" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 4a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 1 1 .708-.708L7.5 10.293V4.5A.5.5 0 0 1 8 4z"/></svg>`
        };

        const btn = document.createElement('button');
        btn.id = btnId;
        btn.type = 'button';
        btn.setAttribute('aria-label', 'Scroll navigation');

        // 定義樣式 (調整大小與加入透明度過渡)
        const styles = {
            position: 'fixed',
            bottom: '20px',
            right: '20px',
            width: '40px',     // 縮小尺寸
            height: '40px',    // 縮小尺寸
            borderRadius: '50%',
            backgroundColor: '#0d6efd',
            border: 'none',
            boxShadow: '0 0.2rem 0.5rem rgba(0,0,0,0.3)',
            zIndex: '10000',
            cursor: 'pointer',
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            // 增加 opacity 的過渡效果
            transition: 'transform 0.2s, background-color 0.2s, opacity 0.5s ease-in-out',
            touchAction: 'manipulation',
            padding: '0',
            opacity: '0.3' // 預設為半透明 (腳本啟動時)
        };

        Object.assign(btn.style, styles);

        // Hover & Active 互動
        btn.onmouseenter = () => { btn.style.transform = 'scale(1.1)'; activeHandler(); };
        btn.onmouseleave = () => { btn.style.transform = 'scale(1)'; };
        btn.onmousedown = () => { btn.style.backgroundColor = '#0b5ed7'; activeHandler(); };
        btn.onmouseup = () => { btn.style.backgroundColor = '#0d6efd'; };
        // 針對手機觸摸也視為「活躍」
        btn.ontouchstart = () => { activeHandler(); };

        document.body.appendChild(btn);

        const State = { TOP: 'top', SCROLLED: 'scrolled' };

        // --- 透明度控制邏輯 ---
        let idleTimer = null;
        const activeHandler = () => {
            // 1. 動作時:顯示按鈕 (不透明)
            btn.style.opacity = '1';
            
            // 2. 重置計時器
            if (idleTimer) clearTimeout(idleTimer);

            // 3. 設定閒置 2 秒後變淡
            idleTimer = setTimeout(() => {
                btn.style.opacity = '0.3'; // 淡出目標透明度
            }, 2000);
        };

        const updateButtonState = () => {
            activeHandler(); // 捲動視為活躍動作

            const scrollTop = window.scrollY || document.documentElement.scrollTop;
            const isAtTop = scrollTop < 50;

            const nextState = isAtTop ? State.TOP : State.SCROLLED;
            if (btn.dataset.state === nextState) return;

            if (isAtTop) {
                btn.innerHTML = icons.down;
                btn.dataset.state = State.TOP;
            } else {
                btn.innerHTML = icons.up;
                btn.dataset.state = State.SCROLLED;
            }
        };

        const handleScrollAction = (e) => {
            e.preventDefault();
            activeHandler(); // 點擊視為活躍動作
            
            if (btn.dataset.state === State.TOP) {
                window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
            } else {
                window.scrollTo({ top: 0, behavior: 'smooth' });
            }
        };

        btn.addEventListener('click', handleScrollAction);
        window.addEventListener('scroll', updateButtonState, { passive: true });
        
        // 初始執行
        updateButtonState();
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();