- // ==UserScript==
- // @name Scroll to Top Button
- // @namespace sttb-ujs-dxrk1e
- // @description Adds a customizable scroll-to-top button near the page bottom.
- // @icon https://i.imgur.com/FxF8TLS.png
- // @match *://*/*
- // @grant none
- // @version 3.1.0
- // @author DXRK1E
- // @license MIT
- // @noframes
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- const _cfg = {
- b: {
- sz: '45px', fs: '18px', bg: '#3a3a3a', hBg: '#555', clr: '#f5f5f5',
- br: '50%', pos: { b: '25px', r: '25px' }, sh: '0 4px 12px rgba(0,0,0,0.4)',
- trMs: 300, z: 2147483647,
- svg: { w: '20px', h: '20px', vb: '0 0 16 16', pd: 'M8 3L14 9L12.6 10.4L8 5.8L3.4 10.4L2 9L8 3Z' },
- lbl: 'Scroll to Top'
- },
- bh: { shThrPx: 300, dDelMs: 150, smScr: true, natSmScr: false },
- sc: { durMs: 800, eas: 'easeInOutCubic' }
- };
-
- const _eas = {
- linear: t => t, easeInQuad: t => t * t, easeOutQuad: t => t * (2 - t),
- easeInOutQuad: t => t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
- easeInCubic: t => t * t * t, easeOutCubic: t => (--t) * t * t + 1,
- easeInOutCubic: t => t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
- easeInQuart: t => t * t * t * t, easeOutQuart: t => 1 - (--t) * t * t * t,
- easeInOutQuart: t => t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t,
- easeInQuint: t => t * t * t * t * t, easeOutQuint: t => 1 + (--t) * t * t * t * t,
- easeInOutQuint: t => t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t,
- easeInExpo: t => (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)),
- easeOutExpo: t => (t === 1) ? 1 : 1 - Math.pow(2, -10 * t),
- easeInOutExpo: t => t === 0 ? 0 : t === 1 ? 1 : t < .5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2
- };
-
- const _bid = 'estb-dxrk1e-s';
- const _sid = 'estb-styles-dxrk1e-s';
-
- let _btn = null;
- let _sto = null;
- let _raf = null;
-
- function _gSP() { return window.scrollY || document.documentElement.scrollTop; }
-
- function _deb(fn, wt) {
- return function (...a) {
- clearTimeout(_sto);
- _sto = setTimeout(() => { fn.apply(this, a); }, wt);
- };
- }
-
- function _gEF() { return _eas[_cfg.sc.eas] || _eas.linear; }
-
- function _injS() {
- if (document.getElementById(_sid)) return;
- const css = `
- #${_bid}{position:fixed;bottom:${_cfg.b.pos.b};right:${_cfg.b.pos.r};width:${_cfg.b.sz};height:${_cfg.b.sz};background-color:${_cfg.b.bg};color:${_cfg.b.clr};border:none;border-radius:${_cfg.b.br};cursor:pointer;box-shadow:${_cfg.b.sh};opacity:0;visibility:hidden;z-index:${_cfg.b.z};transition:opacity ${_cfg.b.trMs}ms ease-in-out,visibility ${_cfg.b.trMs}ms ease-in-out,background-color ${_cfg.b.trMs}ms ease-in-out,transform ${_cfg.b.trMs}ms ease-in-out;display:flex;align-items:center;justify-content:center;padding:0;transform:scale(1);outline:none;will-change:opacity,transform;overflow:hidden;}
- #${_bid}:hover{background-color:${_cfg.b.hBg};transform:scale(1.1);}
- #${_bid}:active{transform:scale(0.95);}
- #${_bid}.visible{opacity:1;visibility:visible;}
- #${_bid} svg{display:block;width:${_cfg.b.svg.w};height:${_cfg.b.svg.h};fill:currentColor;}
- `;
- const se = document.createElement('style');
- se.id = _sid; se.textContent = css;
- (document.head || document.documentElement).appendChild(se);
- }
-
- function _crB() {
- const b = document.createElement('button');
- b.id = _bid; b.setAttribute('aria-label', _cfg.b.lbl); b.setAttribute('title', _cfg.b.lbl); b.type = 'button';
- b.innerHTML = `<svg width="${_cfg.b.svg.w}" height="${_cfg.b.svg.h}" viewBox="${_cfg.b.svg.vb}" xmlns="http://www.w3.org/2000/svg"><path d="${_cfg.b.svg.pd}" /></svg>`;
- b.addEventListener('click', (e) => { e.preventDefault(); _scT(); });
- return b;
- }
-
- function _smS() {
- const sPos = _gSP(); if (sPos <= 0) return;
- const sT = performance.now(); const dur = _cfg.sc.durMs; const easing = _gEF();
- if (_raf) { cancelAnimationFrame(_raf); }
- function step(cT) {
- const el = cT - sT; const prog = Math.min(el / dur, 1);
- const eP = easing(prog); const nPos = sPos * (1 - eP);
- window.scrollTo(0, nPos);
- if (prog < 1) { _raf = requestAnimationFrame(step); } else { _raf = null; }
- }
- _raf = requestAnimationFrame(step);
- }
-
- function _scT() {
- if (_cfg.bh.smScr) {
- if (_cfg.bh.natSmScr && 'scrollBehavior' in document.documentElement.style) {
- window.scrollTo({ top: 0, behavior: 'smooth' });
- } else { _smS(); }
- } else { window.scrollTo({ top: 0, behavior: 'auto' }); }
- }
-
- function _hSE() {
- if (!_btn) return;
- const sPos = _gSP();
- if (sPos > _cfg.bh.shThrPx) { _btn.classList.add('visible'); }
- else { _btn.classList.remove('visible'); }
- }
-
- function _init() {
- if (document.getElementById(_bid) || !document.body) return;
- try {
- _injS(); _btn = _crB(); document.body.appendChild(_btn);
- const dBounce = _deb(_hSE, _cfg.bh.dDelMs);
- window.addEventListener('scroll', dBounce, { passive: true });
- window.addEventListener('resize', dBounce, { passive: true });
- const mObs = new MutationObserver(dBounce);
- mObs.observe(document.body, { childList: true, subtree: true, attributes: false });
- _hSE();
- } catch (e) { console.error("STTB Error:", e); }
- }
-
- if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', _init); }
- else { _init(); }
-
- })();