Touch Scroll Speed Multiplier

Increases the touchscreen scroll speed in most websites.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Touch Scroll Speed Multiplier
// @namespace    http://tampermonkey.net/
// @version      1.4
// @license      MIT
// @description  Increases the touchscreen scroll speed in most websites.
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const MULTIPLIER = 2.5;
    const START_THRESHOLD = 6;

    const FAIL_RATIO = 0.35;      // poniżej tego uznajemy, że scroll nie działa sensownie
    const FAIL_STREAK_LIMIT = 2;  // po tylu porażkach wyłączamy skrypt na stronie
    const STORAGE_KEY = 'tssm_disabled:' + location.hostname;

    let lastY = null;
    let isHandling = false;
    let movedEnough = false;

    let pageDisabled = false;
    let failureStreak = 0;
    let probeDone = false;

    try {
        if (sessionStorage.getItem(STORAGE_KEY) === '1') {
            pageDisabled = true;
        }
    } catch (_) {}

    function disableForThisPage() {
        pageDisabled = true;
        try {
            sessionStorage.setItem(STORAGE_KEY, '1');
        } catch (_) {}
    }

    function getRoot() {
        return document.scrollingElement || document.documentElement;
    }

    function isScrollable(el) {
        if (!(el instanceof Element)) return false;

        const style = getComputedStyle(el);
        const overflowY = style.overflowY;

        return /(auto|scroll|overlay)/.test(overflowY) && el.scrollHeight > el.clientHeight + 1;
    }

    function canScroll(el, delta) {
        const maxScrollTop = el.scrollHeight - el.clientHeight;
        if (maxScrollTop <= 0) return false;

        if (delta > 0) return el.scrollTop < maxScrollTop - 1;
        if (delta < 0) return el.scrollTop > 1;

        return false;
    }

    function getParent(el) {
        if (!el) return null;
        return el.parentElement || (el.getRootNode && el.getRootNode().host) || null;
    }

    function findScrollTarget(x, y, delta) {
        let el = document.elementFromPoint(x, y);

        while (el && el !== document.body && el !== document.documentElement) {
            if (isScrollable(el) && canScroll(el, delta)) {
                return el;
            }
            el = getParent(el);
        }

        const root = getRoot();
        return canScroll(root, delta) ? root : null;
    }

    function getScrollPos(target) {
        const root = getRoot();
        if (target === root) {
            return window.scrollY || window.pageYOffset || 0;
        }
        return target.scrollTop;
    }

    function probeRootScrollOnce() {
        if (probeDone || pageDisabled) return;
        probeDone = true;

        const root = getRoot();
        const maxScrollTop = root.scrollHeight - root.clientHeight;

        if (root.scrollHeight <= root.clientHeight + 5) return;

        const before = getScrollPos(root);
        const step = before < maxScrollTop - 1 ? 1 : -1;
        const testValue = before + step;

        root.scrollTop = testValue;

        const after = getScrollPos(root);
        const works = Math.abs(after - testValue) < 0.5;

        root.scrollTop = before;

        if (!works) {
            disableForThisPage();
        }
    }

    function recordScrollResult(target, before, amount) {
        requestAnimationFrame(() => {
            if (pageDisabled) return;

            const after = getScrollPos(target);
            const moved = Math.abs(after - before);
            const expected = Math.max(1, Math.abs(amount) * FAIL_RATIO);

            if (moved < expected) {
                failureStreak++;
                if (failureStreak >= FAIL_STREAK_LIMIT) {
                    disableForThisPage();
                }
            } else {
                failureStreak = 0;
            }
        });
    }

    function scrollTarget(target, amount) {
        const before = getScrollPos(target);
        const root = getRoot();

        if (target === root) {
            window.scrollBy(0, amount);
        } else if (typeof target.scrollBy === 'function') {
            target.scrollBy(0, amount);
        } else {
            target.scrollTop = before + amount;
        }

        recordScrollResult(target, before, amount);
    }

    function onTouchStart(e) {
        if (pageDisabled) return;

        probeRootScrollOnce();

        if (e.touches && e.touches.length === 1) {
            isHandling = true;
            movedEnough = false;
            lastY = e.touches[0].clientY;
        } else {
            isHandling = false;
            lastY = null;
        }
    }

    function onTouchMove(e) {
        if (pageDisabled) return;

        if (!isHandling || !e.touches || e.touches.length !== 1) {
            isHandling = false;
            movedEnough = false;
            return;
        }

        const touch = e.touches[0];
        const y = touch.clientY;
        const delta = lastY - y;
        lastY = y;

        if (!movedEnough && Math.abs(delta) < START_THRESHOLD) {
            return;
        }

        movedEnough = true;

        const target = findScrollTarget(touch.clientX, touch.clientY, delta);
        if (!target) return;

        e.preventDefault();
        scrollTarget(target, delta * MULTIPLIER);
    }

    function onTouchEnd() {
        isHandling = false;
        movedEnough = false;
        lastY = null;
    }

    document.addEventListener('touchstart', onTouchStart, { passive: true });
    document.addEventListener('touchmove', onTouchMove, { passive: false });
    document.addEventListener('touchend', onTouchEnd, { passive: true });
    document.addEventListener('touchcancel', onTouchEnd, { passive: true });
})();