Increases the touchscreen scroll speed in most websites.
// ==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 });
})();