// ==UserScript==
// @name Scroll to Top Button
// @namespace sttb-ujs-dxrk1e
// @description Adds a scroll to top button at the bottom right of the page when scrolled to the bottom, and hides it at the top.
// @icon https://i.imgur.com/FxF8TLS.png
// @match *://*/*
// @grant none
// @version 2.1.1
// @author DXRK1E
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Configuration object
const CONFIG = {
buttonSize: '40px',
fontSize: '16px',
backgroundColor: '#333',
hoverColor: '#444',
textColor: '#FFF',
borderRadius: '50%',
bottom: '20px',
right: '20px',
showThreshold: 300,
minimumPageHeight: 1000,
fadeSpeed: 300,
debounceDelay: 150,
zIndex: 2147483647,
// Smooth scroll configuration
scroll: {
duration: 800, // Duration of scroll animation in ms
easing: 'easeInOutCubic', // Easing function for smooth acceleration/deceleration
fps: 60, // Frames per second for smooth animation
breakpoints: { // Adjust scroll speed based on distance
short: 500, // px
medium: 1500, // px
long: 3000 // px
}
}
};
// Easing functions
const easingFunctions = {
// Cubic easing in/out
easeInOutCubic: t => t < 0.5
? 4 * t * t * t
: 1 - Math.pow(-2 * t + 2, 3) / 2,
// Quadratic easing out
easeOutQuad: t => 1 - (1 - t) * (1 - t),
// Exponential easing in/out
easeInOutExpo: t => t === 0
? 0
: t === 1
? 1
: t < 0.5
? Math.pow(2, 20 * t - 10) / 2
: (2 - Math.pow(2, -20 * t + 10)) / 2
};
function createScrollButton() {
const button = document.createElement('button');
button.id = 'enhanced-scroll-top-btn';
button.innerHTML = `
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3L14 9L12.6 10.4L8 5.8L3.4 10.4L2 9L8 3Z" fill="currentColor"/>
</svg>
`;
const styles = {
position: 'fixed',
bottom: CONFIG.bottom,
right: CONFIG.right,
width: CONFIG.buttonSize,
height: CONFIG.buttonSize,
fontSize: CONFIG.fontSize,
backgroundColor: CONFIG.backgroundColor,
color: CONFIG.textColor,
border: 'none',
borderRadius: CONFIG.borderRadius,
cursor: 'pointer',
boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
opacity: '0',
visibility: 'hidden',
zIndex: CONFIG.zIndex,
transition: `all ${CONFIG.fadeSpeed}ms ease-in-out`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '0',
transform: 'scale(1)',
outline: 'none'
};
Object.assign(button.style, styles);
button.addEventListener('mouseenter', () => {
button.style.backgroundColor = CONFIG.hoverColor;
button.style.transform = 'scale(1.1)';
});
button.addEventListener('mouseleave', () => {
button.style.backgroundColor = CONFIG.backgroundColor;
button.style.transform = 'scale(1)';
});
button.addEventListener('mousedown', () => {
button.style.transform = 'scale(0.95)';
});
button.addEventListener('mouseup', () => {
button.style.transform = 'scale(1.1)';
});
return button;
}
function smoothScrollToTop() {
const startPosition = window.pageYOffset || document.documentElement.scrollTop;
const startTime = performance.now();
let duration = CONFIG.scroll.duration;
// Adjust duration based on scroll distance
if (startPosition < CONFIG.scroll.breakpoints.short) {
duration *= 0.7; // Faster for short distances
} else if (startPosition > CONFIG.scroll.breakpoints.long) {
duration *= 1.3; // Slower for long distances
}
function scrollStep(currentTime) {
const timeElapsed = currentTime - startTime;
const progress = Math.min(timeElapsed / duration, 1);
const easedProgress = easingFunctions[CONFIG.scroll.easing](progress);
const position = startPosition - (startPosition * easedProgress);
window.scrollTo(0, position);
if (timeElapsed < duration) {
requestAnimationFrame(scrollStep);
}
}
requestAnimationFrame(scrollStep);
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function shouldShowButton() {
const scrollHeight = Math.max(
document.documentElement.scrollHeight,
document.body.scrollHeight
);
const viewportHeight = window.innerHeight;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return (
scrollTop > CONFIG.showThreshold &&
scrollHeight > CONFIG.minimumPageHeight &&
scrollHeight > viewportHeight * 1.5
);
}
function handleScroll() {
const button = document.getElementById('enhanced-scroll-top-btn');
if (!button) return;
if (shouldShowButton()) {
button.style.visibility = 'visible';
button.style.opacity = '1';
} else {
button.style.opacity = '0';
setTimeout(() => {
if (button.style.opacity === '0') {
button.style.visibility = 'hidden';
}
}, CONFIG.fadeSpeed);
}
}
function init() {
const existingButton = document.getElementById('enhanced-scroll-top-btn');
if (existingButton) return;
const button = createScrollButton();
document.body.appendChild(button);
const debouncedScroll = debounce(handleScroll, CONFIG.debounceDelay);
window.addEventListener('scroll', debouncedScroll, { passive: true });
window.addEventListener('resize', debouncedScroll, { passive: true });
const observer = new MutationObserver(debouncedScroll);
observer.observe(document.body, {
childList: true,
subtree: true
});
button.addEventListener('click', (e) => {
e.preventDefault();
smoothScrollToTop();
});
// Initial check
handleScroll();
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();