TopAndDownButtonsEverywhere

Top and Down buttons everywhere. Button has 3 speed zones!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name TopAndDownButtonsEverywhere
// @description Top and Down buttons everywhere. Button has 3 speed zones!
// @version 1.74
// @author Jerry
// @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @include *
// @run-at document-end
// @homepage https://greasyfork.org/en/scripts/441713
// @namespace https://greasyfork.org/en/users/28298
// ==/UserScript==

// author: 2026 Claude, 2018++ Jerry, 2018+ Volkan K., 2014-2016 Max Max
// RANDOM used to generate unique CSS class/ID names to avoid collisions with the host page
var RANDOM = Math.floor(Math.random() * 1234567890);

// [1] skip all iframes
if (window.self !== window.top) {
    return;
}

// add style
function addStyle(css) {
    var head = document.head;
    if (head) {
        var style = document.createElement('style');
        style.type = 'text/css';
        style.appendChild(document.createTextNode(css));
        head.appendChild(style);
    }
}

// global variables
var el = document.documentElement || document.body,
    speed_by_click  = 500, // ms duration for click scroll
    zIindex         = 1001,
    scrollVelocity  = 0.06, // px/ms, updated by mouse position in button
    scrollDir       = 0,    // -1 = up, 1 = down, 0 = stopped
    lastFrameTime   = null,
    rafId           = null;

// update scroll velocity based on vertical mouse position within a button
// invert=false (down btn): top=slow, middle=normal, bottom=fast
// invert=true  (up btn):   top=fast, middle=normal, bottom=slow
function updateScrollStep(e, btn, invert) {
    var rect = btn.getBoundingClientRect();
    var relY = (e.clientY - rect.top) / rect.height;
    if (invert) relY = 1 - relY;
    if (relY < 1 / 3) {
        scrollVelocity = 0.04;  // slow for slow reading
    } else if (relY < 2 / 3) {
        scrollVelocity = 0.06;  // normal
    } else {
        scrollVelocity = 0.18;   // fast for scanning
    }
}

// rAF-based scroll loop — frame-synced, no jitter
function scrollLoop(timestamp) {
    if (scrollDir === 0) {
        lastFrameTime = null;
        rafId = null;
        return;
    }
    if (lastFrameTime !== null) {
        var delta = timestamp - lastFrameTime;
        var pos   = window.pageYOffset || document.documentElement.scrollTop;
        window.scrollTo(0, pos + scrollDir * scrollVelocity * delta);
    }
    lastFrameTime = timestamp;
    rafId = requestAnimationFrame(scrollLoop);
}

function move_up() {
    scrollDir = -1;
    if (!rafId) rafId = requestAnimationFrame(scrollLoop);
}

function move_dn() {
    scrollDir = 1;
    if (!rafId) rafId = requestAnimationFrame(scrollLoop);
}

function stop_scroll() {
    scrollDir     = 0;
    lastFrameTime = null;
    if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
}

// smooth scroll to position with duration via requestAnimationFrame
function smoothScrollTo(targetY, duration) {
    var startY    = window.pageYOffset || document.documentElement.scrollTop;
    var distance  = targetY - startY;
    var startTime = null;

    function step(timestamp) {
        if (!startTime) startTime = timestamp;
        var elapsed  = timestamp - startTime;
        var progress = Math.min(elapsed / duration, 1);
        // ease in-out quad
        progress = progress < 0.5
            ? 2 * progress * progress
            : -1 + (4 - 2 * progress) * progress;
        window.scrollTo(0, startY + distance * progress);
        if (elapsed < duration) {
            requestAnimationFrame(step);
        }
    }
    requestAnimationFrame(step);
}

function goto_up() {
    smoothScrollTo(0, speed_by_click);
}

function goto_dn() {
    smoothScrollTo(getDocumentHeight(), speed_by_click);
}

// document height
function getDocumentHeight() {
    var body = document.body;
    return (body.scrollHeight > body.offsetHeight) ? body.scrollHeight : body.offsetHeight;
}

// check if page is scrollable in given dimension
function get_scroll(a) {
    var d = document,
        b = d.body,
        e = d.documentElement,
        c = 'client' + a,
        s = 'scroll' + a;
    return /CSS/.test(d.compatMode) ? (e[c] < e[s]) : (b[c] < b[s]);
}

// add CSS
function shareCSS() {
    var img_up = 'data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB+SURBVDhPY1i1atV/amAGahgCMoNhaIGlS5cKAp19BoRBbLJcj2QILDJINwzoAmMgfoclIkBixkS5DI8hMJcRNgxoSBoOl6CnNZBhaVhdBjWE1MSJahjQkA4KEmYH2GUrV66cSYEhYB+AzKBtFiHkQqKiH6Ro1CDCQTWgYQQAs81DU0G/83sAAAAASUVORK5CYII=';
    var img_dn = 'data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACPSURBVDhPY2DAAlatWvUfH8amB6vYqEGEg2pgw4iQ7cTKM6xcuXImsYpxqQOZAQ4woIIOCgzrQAl1oEFpZBiWhitFgwx7R4SBIDXYDYGZDFRgTMAwkCHGhBMRJMxwGUa8ITCbli5dKgg08AySN8+AxIhyCboiJMPIN4Qsm6miiYioxltawvSDYogohYTUAQC80UNTOht/YwAAAABJRU5ErkJggg==';

    var s = '';
    s += '#play_btn_up' + RANDOM + ' { position:fixed; right:45%; bottom:90%; z-index:' + zIindex + '; height:50px; width:10%; cursor:pointer; background:url(' + img_up + ') no-repeat scroll 50% 50% rgba(0,0,0,0.7); border-radius:5px; margin-top:-24px; }';
    s += '#play_btn_dn' + RANDOM + ' { position:fixed; right:45%; top:90%;    z-index:' + zIindex + '; height:50px; width:10%; cursor:pointer; background:url(' + img_dn + ') no-repeat scroll 50% 50% rgba(0,0,0,0.7); border-radius:5px; margin-top:-24px; }';
    s += '.play_btn'    + RANDOM + ' { transition-duration:0.5s; opacity:0.01; }';
    s += '.play_btn'    + RANDOM + ':hover { opacity:1; }';
    addStyle(s);
}

// main
function create_btn_element() {
    var h = get_scroll('Height');
    if (!h) return;

    shareCSS();

    if (el) {
        var up = document.createElement('span');
        var dn = document.createElement('span');

        up.setAttribute('id', 'play_btn_up' + RANDOM);
        dn.setAttribute('id', 'play_btn_dn' + RANDOM);
        up.className = 'play_btn' + RANDOM;
        dn.className = 'play_btn' + RANDOM;

        document.body.appendChild(up);
        document.body.appendChild(dn);

        var scrolled = window.pageYOffset || document.documentElement.scrollTop;
        up.style.display = (scrolled > 0) ? '' : 'none';

        up.addEventListener('mouseover', move_up, false);
        dn.addEventListener('mouseover', move_dn, false);
        up.addEventListener('mousemove', function(e) { updateScrollStep(e, up, true); }, false);
        dn.addEventListener('mousemove', function(e) { updateScrollStep(e, dn, false); }, false);
        up.addEventListener('mouseout', stop_scroll, false);
        dn.addEventListener('mouseout', stop_scroll, false);
        up.addEventListener('click', goto_up, false);
        dn.addEventListener('click', goto_dn, false);

        window.addEventListener('scroll', function() {
            var scrolled    = window.pageYOffset || document.documentElement.scrollTop;
            var diffHeight  = document.body.scrollHeight - window.innerHeight;
            up.style.display = (scrolled > 0)         ? '' : 'none';
            dn.style.display = (diffHeight > scrolled) ? '' : 'none';
        });
    }
}

create_btn_element();