Top and Down buttons everywhere. Button has 3 speed zones!
// ==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();