// ==UserScript==
// @name Scroll Limiter
// @namespace https://www.qs5.org/?Scroll-Limiter
// @version 1.0
// @description Limits excessive scrolling on social media.
// @author kmfb@github
// @match *://*.facebook.com/*
// @match *://*.twitter.com/*
// @match *://*.x.com/*
// @match *://*.weibo.com/*
// @match *://*.zhihu.com/*
// @match *://*.xiaohongshu.com/*
// @match *://*.bilibili.com/*
// @match *://*.reddit.com/*
// @match *://*.youtube.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const DEFAULTS = {
SCROLL_LIMIT: 8000,
PROGRESS_BAR_CONFIG: {
HEIGHT: "4px",
COLOR: "#4CAF50",
HIDE_DELAY: 1000, // ms
},
NOTIFICATION_CONFIG: {
DURATION: 3000, // ms
BACKGROUND: "#333",
TEXT_COLOR: "#fff",
},
};
const state = {
scrollLimit: DEFAULTS.SCROLL_LIMIT,
hasReachedLimit: false,
progressBar: null,
scrollTimeout: null,
enabled: true,
};
const progressBarManager = {
create() {
const bar = document.createElement("div");
Object.assign(bar.style, {
position: "fixed",
top: "0",
left: "0",
height: DEFAULTS.PROGRESS_BAR_CONFIG.HEIGHT,
backgroundColor: DEFAULTS.PROGRESS_BAR_CONFIG.COLOR,
transition: "width 0.2s",
zIndex: "9999",
});
document.body.appendChild(bar);
return bar;
},
update(percentage) {
if (state.progressBar) {
state.progressBar.style.width = `${percentage}%`;
}
},
remove() {
if (state.progressBar) {
state.progressBar.remove();
state.progressBar = null;
}
},
};
const notificationManager = {
show() {
this.removeExisting();
const notification = document.createElement("div");
Object.assign(notification.style, {
position: "fixed",
top: "10px",
right: "10px",
backgroundColor: DEFAULTS.NOTIFICATION_CONFIG.BACKGROUND,
color: DEFAULTS.NOTIFICATION_CONFIG.TEXT_COLOR,
padding: "10px",
borderRadius: "5px",
zIndex: "10000",
});
notification.className = "scroll-limit-notification";
notification.textContent = "You've reached your scrolling limit!";
document.body.appendChild(notification);
setTimeout(() => notification.remove(), DEFAULTS.NOTIFICATION_CONFIG.DURATION);
},
removeExisting() {
document.querySelectorAll(".scroll-limit-notification").forEach(notification => notification.remove());
},
};
function handleScroll() {
if (!state.enabled) return;
if (!state.progressBar) {
state.progressBar = progressBarManager.create();
}
clearTimeout(state.scrollTimeout);
state.scrollTimeout = setTimeout(() => {
progressBarManager.remove();
}, DEFAULTS.PROGRESS_BAR_CONFIG.HIDE_DELAY);
const scrollPercentage = (window.scrollY / state.scrollLimit) * 100;
progressBarManager.update(scrollPercentage);
if (window.scrollY >= state.scrollLimit) {
window.scrollTo(0, state.scrollLimit);
notificationManager.show();
} else {
state.hasReachedLimit = false;
}
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const debouncedHandleScroll = debounce(handleScroll, 16);
window.addEventListener("scroll", debouncedHandleScroll);
// Initialize
window.addEventListener("scroll", handleScroll);
})();