Greasy Fork

Greasy Fork is available in English.

页面导航

在右下角增添可以置顶或置底的按钮,长按可拖动

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         页面导航
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  在右下角增添可以置顶或置底的按钮,长按可拖动
// @author       G31415
// @match        *://*/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    const id = 'g' + Math.random().toString(36).slice(2,7);
    const btn = Object.assign(document.createElement('button'), {
        id,
        textContent: '▼',
        title: '点击跳转'
    });
    
    // 状态变量
    let isDragging = false, dragActive = false, pressTimer = null;
    let startX, startY, startRight, startBottom;
    let lastY = window.scrollY;

    const style = document.createElement('style');
    style.textContent = `
        #${id} {
            all: initial;
            position: fixed;
            bottom: 0;
            right: 0;
            width: 32px;
            height: 32px;
            font: 16px/32px sans-serif;
            text-align: center;
            color: #000;
            background: transparent;
            border: none;
            cursor: pointer;
            z-index: 9999;
            user-select: none;
            -webkit-tap-highlight-color: transparent;
            outline: none; /* 取消蓝色方框 */
            display: none;
            touch-action: none; /* 核心:防止移动端手势干扰拖拽 */
        }
        #${id}.visible { display: block; }
        #${id}:hover { transform: scale(1); }
        #${id}:active { transform: scale(0.82); }
        #${id}.dragging {
            opacity: 0.6;
            cursor: grabbing;
            transform: scale(1);
            transition: none;
        }
        @media (max-width: 768px) {
            #${id} { width: 44px; height: 44px; font-size: 26px; line-height: 44px; }
        }
    `;
    
    document.head.appendChild(style);
    document.body.appendChild(btn);

    // 拖拽逻辑更新为 Pointer 事件(支持鼠标和触摸)
    const handlePointerMove = (e) => {
        if (!dragActive) return;
        
        const deltaX = e.clientX - startX;
        const deltaY = e.clientY - startY;
        
        // 计算新位置并限制在视口内
        let newRight = Math.max(0, Math.min(window.innerWidth - btn.offsetWidth, startRight - deltaX));
        let newBottom = Math.max(0, Math.min(window.innerHeight - btn.offsetHeight, startBottom - deltaY));
        
        btn.style.right = newRight + 'px';
        btn.style.bottom = newBottom + 'px';
    };

    btn.onpointerdown = (e) => {
        if (e.button !== 0 && e.pointerType === 'mouse') return;
        
        pressTimer = setTimeout(() => {
            dragActive = isDragging = true;
            btn.classList.add('dragging');
            // 锁定指针,拖拽更稳
            btn.setPointerCapture(e.pointerId);
            
            startX = e.clientX;
            startY = e.clientY;
            const s = getComputedStyle(btn);
            startRight = parseFloat(s.right) || 0;
            startBottom = parseFloat(s.bottom) || 0;
        }, 500);
    };

    btn.onpointerup = (e) => {
        clearTimeout(pressTimer);
        if (dragActive) {
            dragActive = isDragging = false;
            btn.classList.remove('dragging');
            btn.releasePointerCapture(e.pointerId);
        } else if (pressTimer && btn.classList.contains('visible')) {
            // 短按触发跳转
            const isToTop = btn.textContent === '▲';
            window.scrollTo(0, isToTop ? 0 : document.documentElement.scrollHeight);
        }
        pressTimer = null;
    };

    // 全局移动监听
    window.addEventListener('pointermove', handlePointerMove);

    // 滚动监听(增加性能节流)
    let ticking = false;
    window.addEventListener('scroll', () => {
        if (!ticking) {
            window.requestAnimationFrame(() => {
                const y = window.scrollY;
                if (y > 0) btn.classList.add('visible');
                
                if (btn.classList.contains('visible') && Math.abs(y - lastY) > 5) {
                    const isDown = y > lastY;
                    btn.textContent = isDown ? '▼' : '▲';
                    btn.title = isDown ? '点击跳转到底部' : '点击跳转到顶部';
                }
                lastY = y;
                ticking = false;
            });
            ticking = true;
        }
    }, { passive: true });

    // 守护按钮不被意外删除
    new MutationObserver(muts => muts.forEach(m => 
        m.removedNodes.forEach(n => n.id === id && document.body.appendChild(btn))
    )).observe(document.body, { childList: true });

})();