Smooth Scroll Function

Winceptor created this userscript, which uses JavaScript to enable smooth page scrolling. I just made it better.

目前為 2024-07-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name Smooth Scroll Function
// @description Winceptor created this userscript, which uses JavaScript to enable smooth page scrolling. I just made it better.
// @author DXRK1E
// @icon https://i.imgur.com/IAwk6NN.png
// @include *
// @exclude     https://www.youtube.com/*
// @exclude     https://mail.google.com/*
// @version 1.3
// @namespace sttb-dxrk1e
// @license MIT
// ==/UserScript==

(function () {
    var Smoothscroll = {};

    // settings
    Smoothscroll.Smoothness = 0.5;
    Smoothscroll.Acceleration = 0.5;

    // debug
    Smoothscroll.Debug = 0; // 0-none, 1-some, etc.

    // automatically calculated
    Smoothscroll.Refreshrate = 60;
    Smoothscroll.MaxRefreshrate = 300;
    Smoothscroll.MinRefreshrate = 1;

    // scrolling and animation
    function ScrollSubpixels(element, newvalue) {
        if (newvalue !== undefined) {
            element.scrollsubpixels = newvalue;
            return newvalue;
        } else {
            var olddelta = element.scrollsubpixels;
            if (olddelta !== undefined) {
                return olddelta;
            }
            return 0;
        }
    }

    function ScrollPixels(element, newvalue) {
        if (newvalue !== undefined) {
            element.scrollpixels = newvalue;
            ScrollSubpixels(element, 0);
            return newvalue;
        } else {
            var olddelta = element.scrollpixels;
            if (olddelta !== undefined) {
                return olddelta;
            }
            return 0;
        }
    }

    function AnimateScroll(target, refreshrate) {
        var scrollsubpixels = ScrollSubpixels(target);
        var scrollpixels = ScrollPixels(target);

        var scrolldirection = scrollpixels > 0 ? 1 : scrollpixels < 0 ? -1 : 0;
        var scrollratio = 1 - Math.pow(refreshrate, -1 / (refreshrate * Smoothscroll.Smoothness));
        var scrollrate = scrollpixels * scrollratio;

        if (Math.abs(scrollpixels) > 2) {
            var fullscrolls = Math.floor(Math.abs(scrollrate)) * scrolldirection;
            var scrollsubpixelsadded = scrollrate - fullscrolls;
            var additionalscrolls = Math.floor(Math.abs(scrollsubpixels + scrollsubpixelsadded)) * scrolldirection;
            var scrollsubpixelsleft = scrollsubpixels + scrollsubpixelsadded - additionalscrolls;

            ScrollPixels(target, scrollpixels - fullscrolls - additionalscrolls);
            ScrollSubpixels(target, scrollsubpixelsleft);

            var scrolldelta = fullscrolls + additionalscrolls;

            target.style.scrollBehavior = "auto"; // fix for pages with changed scroll-behavior
            target.scrollTop = target.scrollTop + scrolldelta;

            target.scrollanimated = true;
            RequestAnimationUpdate(function (newrefreshrate) {
                AnimateScroll(target, newrefreshrate);
            });
        } else {
            RequestAnimationUpdate(function (newrefreshrate) {
                ScrollPixels(target, 0);
            });
            target.scrollanimated = false;
        }
    }

    function RequestAnimationUpdate(cb) {
        var before = performance.now();
        window.requestAnimationFrame(function () {
            var after = performance.now();
            var frametime = after - before;
            var calculatedFps = 1000 / Math.max(frametime, 1);
            var refreshrate = Math.min(Math.max(calculatedFps, Smoothscroll.MinRefreshrate), Smoothscroll.MaxRefreshrate);

            cb(refreshrate);
        });
    }

    Smoothscroll.Stop = function (target) {
        if (target) {
            ScrollPixels(target, 0);
        }
    };

    Smoothscroll.Start = function (target, scrollamount) {
        if (target) {
            var scrolltotal = ScrollPixels(target, scrollamount);
            if (!target.scrollanimated) {
                AnimateScroll(target, Smoothscroll.Refreshrate);
            }
        }
    };

    function CanScroll(element, dir) {
        if (dir < 0) {
            return element.scrollTop > 0;
        }
        if (dir > 0) {
            if (element === document.body) {
                if (element.scrollTop === 0) {
                    element.scrollTop = 3;
                    if (element.scrollTop === 0) {
                        return false;
                    }
                    element.scrollTop = 0;
                }
                return Math.round(element.clientHeight + element.scrollTop) < element.offsetHeight;
            }
            return Math.round(element.clientHeight + element.scrollTop) < element.scrollHeight;
        }
    }

    function HasScrollbar(element) {
        if (element === window || element === document) {
            return false;
        }
        if (element === document.body) {
            return window.getComputedStyle(document.body)['overflow-y'] !== "hidden";
        }
        if (element === document.documentElement) {
            return window.innerWidth > document.documentElement.clientWidth;
        } else {
            var style = window.getComputedStyle(element);
            return style['overflow-y'] !== "hidden" && style['overflow-y'] !== "visible";
        }
    }

    function Scrollable(element, dir) {
        if (element === document.body) {
            // return false;
        }
        var scrollablecheck = CanScroll(element, dir);
        if (!scrollablecheck) {
            return false;
        }
        var scrollbarcheck = HasScrollbar(element);
        if (!scrollbarcheck) {
            return false;
        }
        return true;
    }

    function GetPath(e) {
        if (e.path) {
            return e.path;
        }
        if (e.composedPath) {
            return e.composedPath();
        }
        return null;
    }

    function GetTarget(e) {
        var direction = e.deltaY;
        var nodes = GetPath(e);
        if (!nodes) {
            return null;
        }
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            if (Scrollable(node, direction)) {
                return node;
            }
        }
        return null;
    }

    function GetStyleProperty(el, styleprop) {
        if (window.getComputedStyle) {
            var heightprop = document.defaultView.getComputedStyle(el, null).getPropertyValue(styleprop);
            if (heightprop) {
                return parseInt(heightprop);
            }
        } else if (el.currentStyle) {
            var heightprop = el.currentStyle[styleprop.encamel()];
            if (heightprop) {
                return parseInt(heightprop);
            }
        }
        return null;
    }

    function StopScroll(e) {
        var nodes = GetPath(e);
        if (!nodes) {
            return null;
        }
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            Smoothscroll.Stop(node);
        }
    }

    function StartScroll(e, target) {
        if (e.defaultPrevented) {
            return true;
        } else {
            var delta = e.deltaY;
            if (e.deltaMode && e.deltaMode === 1) {
                var line = GetStyleProperty(target, 'line-height');
                if (line && line > 0) {
                    delta = e.deltaY * line;
                }
            }
            if (e.deltaMode && e.deltaMode === 2) {
                var page = target.clientHeight;
                if (page && page > 0) {
                    delta = e.deltaY * page;
                }
            }
            var scrollpixels = ScrollPixels(target);
            var accelerationratio = Math.sqrt(Math.abs(scrollpixels / delta * Smoothscroll.Acceleration));
            var acceleration = Math.round(delta * accelerationratio);
            var totalscroll = scrollpixels + delta + acceleration;
            Smoothscroll.Start(target, totalscroll);
            e.preventDefault();
        }
    }

    function WheelEvent(e) {
        var target = GetTarget(e);
        if (target) {
            StartScroll(e, target);
        }
    }

    function ClickEvent(e) {
        StopScroll(e);
    }

    function Init() {
        if (window.top !== window.self) {
            return null;
        }
        if (window.Smoothscroll && window.Smoothscroll.Loaded) {
            return null;
        }
        if (!window.requestAnimationFrame) {
            window.requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame;
        }
        document.documentElement.addEventListener("wheel", function (e) {
            WheelEvent(e);
        }, { passive: false });
        document.documentElement.addEventListener("mousedown", function (e) {
            ClickEvent(e);
        });
        window.Smoothscroll = Smoothscroll;
        window.Smoothscroll.Loaded = true;
        console.log("Smoothscroll: loaded");
    }
    Init();
})();


// Ignore this; it's just a credits screen that appears on the devtools console only.
console.log(
    '%cDarkPulse\n%cUSERSCRIPT: %cSmooth Scroll Function\n%cDeveloped by %cDXRK1E',
    'color: #8C52FF; font-size: 36px; font-weight: bold; margin: 20px 0;',
    'color: white; font-size: 20px; font-weight: bold; margin: 20px 0 0 20px;',
    'color: #B0BFFB; font-size: 20px; font-weight: bold; margin: 20px 0;',
    'color: white; font-size: 16px; margin: 20px 0 0 20px;',
    'color: #615EFC; font-size: 16px; font-weight: bold; margin: 20px 0;'
);