Scroll Limiter

Limits excessive scrolling on social media.

  1. // ==UserScript==
  2. // @name Scroll Limiter
  3. // @namespace https://www.qs5.org/?Scroll-Limiter
  4. // @version 1.0
  5. // @description Limits excessive scrolling on social media.
  6. // @author kmfb@github
  7. // @match *://*.facebook.com/*
  8. // @match *://*.twitter.com/*
  9. // @match *://*.x.com/*
  10. // @match *://*.weibo.com/*
  11. // @match *://*.zhihu.com/*
  12. // @match *://*.xiaohongshu.com/*
  13. // @match *://*.bilibili.com/*
  14. // @match *://*.reddit.com/*
  15. // @match *://*.youtube.com/*
  16. // @grant none
  17. // ==/UserScript==
  18.  
  19. (function() {
  20. 'use strict';
  21.  
  22. const DEFAULTS = {
  23. SCROLL_LIMIT: 8000,
  24. PROGRESS_BAR_CONFIG: {
  25. HEIGHT: "4px",
  26. COLOR: "#4CAF50",
  27. HIDE_DELAY: 1000, // ms
  28. },
  29. NOTIFICATION_CONFIG: {
  30. DURATION: 3000, // ms
  31. BACKGROUND: "#333",
  32. TEXT_COLOR: "#fff",
  33. },
  34. };
  35.  
  36. const state = {
  37. scrollLimit: DEFAULTS.SCROLL_LIMIT,
  38. hasReachedLimit: false,
  39. progressBar: null,
  40. scrollTimeout: null,
  41. enabled: true,
  42. };
  43.  
  44. const progressBarManager = {
  45. create() {
  46. const bar = document.createElement("div");
  47. Object.assign(bar.style, {
  48. position: "fixed",
  49. top: "0",
  50. left: "0",
  51. height: DEFAULTS.PROGRESS_BAR_CONFIG.HEIGHT,
  52. backgroundColor: DEFAULTS.PROGRESS_BAR_CONFIG.COLOR,
  53. transition: "width 0.2s",
  54. zIndex: "9999",
  55. });
  56. document.body.appendChild(bar);
  57. return bar;
  58. },
  59. update(percentage) {
  60. if (state.progressBar) {
  61. state.progressBar.style.width = `${percentage}%`;
  62. }
  63. },
  64. remove() {
  65. if (state.progressBar) {
  66. state.progressBar.remove();
  67. state.progressBar = null;
  68. }
  69. },
  70. };
  71.  
  72. const notificationManager = {
  73. show() {
  74. this.removeExisting();
  75. const notification = document.createElement("div");
  76. Object.assign(notification.style, {
  77. position: "fixed",
  78. top: "10px",
  79. right: "10px",
  80. backgroundColor: DEFAULTS.NOTIFICATION_CONFIG.BACKGROUND,
  81. color: DEFAULTS.NOTIFICATION_CONFIG.TEXT_COLOR,
  82. padding: "10px",
  83. borderRadius: "5px",
  84. zIndex: "10000",
  85. });
  86. notification.className = "scroll-limit-notification";
  87. notification.textContent = "You've reached your scrolling limit!";
  88. document.body.appendChild(notification);
  89. setTimeout(() => notification.remove(), DEFAULTS.NOTIFICATION_CONFIG.DURATION);
  90. },
  91. removeExisting() {
  92. document.querySelectorAll(".scroll-limit-notification").forEach(notification => notification.remove());
  93. },
  94. };
  95.  
  96. function handleScroll() {
  97. if (!state.enabled) return;
  98.  
  99. if (!state.progressBar) {
  100. state.progressBar = progressBarManager.create();
  101. }
  102.  
  103. clearTimeout(state.scrollTimeout);
  104. state.scrollTimeout = setTimeout(() => {
  105. progressBarManager.remove();
  106. }, DEFAULTS.PROGRESS_BAR_CONFIG.HIDE_DELAY);
  107.  
  108. const scrollPercentage = (window.scrollY / state.scrollLimit) * 100;
  109. progressBarManager.update(scrollPercentage);
  110.  
  111. if (window.scrollY >= state.scrollLimit) {
  112. window.scrollTo(0, state.scrollLimit);
  113. notificationManager.show();
  114. } else {
  115. state.hasReachedLimit = false;
  116. }
  117. }
  118.  
  119. function debounce(func, wait) {
  120. let timeout;
  121. return function executedFunction(...args) {
  122. const later = () => {
  123. clearTimeout(timeout);
  124. func(...args);
  125. };
  126. clearTimeout(timeout);
  127. timeout = setTimeout(later, wait);
  128. };
  129. }
  130.  
  131. const debouncedHandleScroll = debounce(handleScroll, 16);
  132. window.addEventListener("scroll", debouncedHandleScroll);
  133.  
  134. // Initialize
  135. window.addEventListener("scroll", handleScroll);
  136. })();