Scroll Page Progress

Visual indicator of page progress while scrolling

当前为 2024-08-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @license MIT
  3. // @name Scroll Page Progress
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.6
  6. // @description Visual indicator of page progress while scrolling
  7. // @author You
  8. // @match *://*/*
  9. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15. let globalShadow
  16.  
  17. function once(fn, context) {
  18. var result;
  19.  
  20. return function() {
  21. if(fn) {
  22. result = fn.apply(context || this, arguments);
  23. fn = null;
  24. }
  25.  
  26. return result;
  27. };
  28. }
  29.  
  30. function insertCirculaProgressBarEl() {
  31. const shadowHost = document.createElement('div')
  32. const shadow = shadowHost.attachShadow({ mode: "closed" });
  33. const circularProgressBar = document.createElement('div')
  34. const closeOverlay = document.createElement('div')
  35. const title = document.createElement('div')
  36. const overlay = document.createElement('div')
  37. const leftSide = document.createElement('div')
  38. const rightSide = document.createElement('div')
  39.  
  40. globalShadow = shadow
  41.  
  42. circularProgressBar.classList.add('circular-progress-bar')
  43. title.classList.add('title');
  44. closeOverlay.classList.add('close-overlay');
  45. overlay.classList.add('overlay');
  46. leftSide.classList.add('left-side');
  47. rightSide.classList.add('right-side');
  48.  
  49. shadowHost.id = 'host-shwadow-circular-progress'
  50. title.innerText = '-%'
  51. closeOverlay.innerHTML = '×'
  52. closeOverlay.addEventListener('click', function() {
  53. circularProgressBar.style.display = 'none'
  54. const path = window.location.pathname;
  55. let savedPaths = JSON.parse(localStorage.getItem('not-allowed-paths')) || [];
  56. console.log(path, savedPaths)
  57. if (!savedPaths.includes(path)) {
  58. savedPaths.push(path);
  59. localStorage.setItem('not-allowed-paths', JSON.stringify(savedPaths));
  60. }
  61. });
  62.  
  63. ;[closeOverlay, title, overlay, leftSide, rightSide].forEach(childEl => circularProgressBar.appendChild(childEl))
  64. shadow.appendChild(circularProgressBar)
  65. document.body.appendChild(shadowHost)
  66. }
  67.  
  68. function addCSS() {
  69. const styleSheet = document.createElement('style');
  70. styleSheet.textContent = `
  71. * {
  72. box-sizing: border-box;
  73. padding: 0;
  74. margin: 0;
  75. }
  76. .circular-progress-bar {
  77. --backgroundColor: #424242;
  78. --left-side-angle: 180deg;
  79. --barColor:orangered;
  80. width: 60px;
  81. height: 60px;
  82. color: #fff;
  83. border-radius: 50%;
  84. overflow: hidden;
  85. position: fixed;
  86. z-index: 999999999;
  87. background: var(--backgroundColor);
  88. border: 5px solid white;
  89. box-shadow:
  90. 0 1px 1px hsl(0deg 0% 0% / 0.075),
  91. 0 2px 2px hsl(0deg 0% 0% / 0.075),
  92. 0 4px 4px hsl(0deg 0% 0% / 0.075),
  93. 0 8px 8px hsl(0deg 0% 0% / 0.075),
  94. 0 16px 16px hsl(0deg 0% 0% / 0.075);
  95. text-align: center;
  96. cursor: pointer;
  97. transition: opacity 0.2s ease;
  98. }
  99. .circular-progress-bar .overlay {
  100. width: 50%;
  101. height: 100%;
  102. position: absolute;
  103. top: 0;
  104. left: 0;
  105. z-index: 1;
  106. background-color: var(--backgroundColor);
  107. }
  108. .close-overlay {
  109. width: 100%;
  110. height: 100%;
  111. position: absolute;
  112. z-index: 101;
  113. display: flex;
  114. justify-content: center;
  115. align-items: center;
  116. /* overflow: hidden; */
  117. border-radius: 50%;
  118. background: red;
  119. font-size: 25px;
  120. text-align: center;
  121. /* line-height: 82px; */
  122. opacity: 0;
  123. }
  124. .circular-progress-bar:hover .close-overlay {
  125. opacity:1;
  126. }
  127. .circular-progress-bar .title {
  128. font-size: 15px;
  129. font-weight: bold;
  130. position:relative;
  131. height: 100%;
  132. display:flex;
  133. justify-content:center;
  134. align-items: center;
  135. z-index: 100;
  136. }
  137.  
  138. .circular-progress-bar .left-side,
  139. .circular-progress-bar .right-side {
  140. width: 50%;
  141. height: 100%;
  142. position: absolute;
  143. top: 0;
  144. left: 0;
  145. border: 5px solid var(--barColor);
  146. border-radius: 100px 0px 0px 100px;
  147. border-right: 0;
  148. transform-origin: right;
  149. }
  150. .circular-progress-bar .left-side {
  151. transform: rotate(var(--left-side-angle));
  152. z-index: var(--zindex);
  153. }
  154. .circular-progress-bar .right-side {
  155. transform: rotate(var(--right-side-angle));
  156. }
  157. `
  158. globalShadow.appendChild(styleSheet)
  159. }
  160.  
  161. insertCirculaProgressBarEl()
  162. addCSS()
  163.  
  164. const currentState = {
  165. deg: 0,
  166. progress: 0,
  167. zIndex: 0
  168. }
  169.  
  170. function setAngle(deg) {
  171. const progressBar = globalShadow.querySelector('.circular-progress-bar')
  172. const leftSide = globalShadow.querySelector('.left-side')
  173. const rightSide = globalShadow.querySelector('.right-side')
  174. leftSide.style.opacity = "0"
  175.  
  176. const zIndex = deg > 180 ? 100 : 0
  177. const rightSideAngle = deg < 180 ? deg : 180
  178. const leftSideAngle = deg
  179. const zIndexChangedToPositive = currentState.zIndex === 0 && zIndex === 100
  180. if (deg > 180) {
  181. leftSide.style.opacity = "1"
  182.  
  183. progressBar.style.setProperty('--left-side-angle', `${leftSideAngle}deg`);
  184. progressBar.style.setProperty('--zindex', zIndex);
  185. }
  186. progressBar.style.setProperty('--right-side-angle', `${rightSideAngle}deg`);
  187. currentState.deg = deg
  188. /*if (zIndex === 100 && zIndexChangedToPositive) {
  189. setUnsetOpacity()
  190. }*/
  191. }
  192. function percentageToAngle (percentageNumber) {
  193. if (percentageNumber > 100) {
  194. return 360
  195. }
  196. if (percentageNumber < 0) {
  197. return 0
  198. }
  199. return (360 * percentageNumber)/100
  200. }
  201.  
  202. function setPercentage(percentageNumber) {
  203. const angle = percentageToAngle(percentageNumber)
  204. setAngle(angle)
  205. }
  206.  
  207. function debounce(callback, wait) {
  208. let timerId;
  209. return (...args) => {
  210. clearTimeout(timerId);
  211. timerId = setTimeout(() => {
  212. callback(...args);
  213. }, wait);
  214. };
  215. }
  216. const progressBar = globalShadow.querySelector('.circular-progress-bar')
  217. const body = document.body;
  218.  
  219. let offsetX = 0, offsetY = 0, isDragging = false;
  220.  
  221. progressBar.addEventListener("mousedown", (e) => {
  222. e.preventDefault()
  223. offsetX = e.clientX - progressBar.offsetLeft;
  224. offsetY = e.clientY - progressBar.offsetTop;
  225. isDragging = true;
  226. progressBar.style.cursor = "grabbing";
  227. progressBar.style.opacity = "0.3";
  228. });
  229.  
  230. document.addEventListener("mousemove", (e) => {
  231. if (isDragging) {
  232. e.preventDefault();
  233. const left = e.clientX - offsetX;
  234. const top = e.clientY - offsetY;
  235. progressBar.style.left = `${left}px`;
  236. progressBar.style.top = `${top}px`;
  237. savePosition(left, top); // Guardar la posición cada vez que se mueve
  238. }
  239. });
  240.  
  241. document.addEventListener("mouseup", () => {
  242. isDragging = false;
  243. progressBar.style.cursor = "grab";
  244. progressBar.style.opacity = "1";
  245. });
  246.  
  247. function savePosition(left, top) {
  248. const position = { left, top };
  249. localStorage.setItem("elementPosition", JSON.stringify(position));
  250. }
  251.  
  252. function loadPosition() {
  253. const savedPosition = localStorage.getItem("elementPosition");
  254. if (savedPosition) {
  255. const { left, top } = JSON.parse(savedPosition);
  256. progressBar.style.left = `${left}px`;
  257. progressBar.style.top = `${top}px`;
  258. } else {
  259. progressBar.style.right = `10px`;
  260. progressBar.style.top = `10px`;
  261. }
  262. }
  263.  
  264. function getCurrentScrollProgress() {
  265. const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
  266. const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
  267. const progress = (winScroll / height) * 100;
  268. return Math.trunc(progress);
  269. }
  270. const progressBarTitle = globalShadow.querySelector('.title')
  271. document.onreadystatechange = function () {
  272. if (document.readyState == "complete") {
  273. const currentPath = window.location.pathname;
  274. const savedPaths = JSON.parse(localStorage.getItem('not-allowed-paths')) || [];
  275.  
  276. if (savedPaths.includes(currentPath)) {
  277. progressBar.style.display = 'none';
  278. return;
  279. }
  280. loadPosition()
  281. document.addEventListener('scroll', debounce(() => {
  282. setPercentage(getCurrentScrollProgress())
  283. progressBarTitle.innerText = getCurrentScrollProgress() + '%'
  284. console.log(progressBarTitle, getCurrentScrollProgress())
  285. currentState.progress = getCurrentScrollProgress()
  286. currentState.deg = percentageToAngle(getCurrentScrollProgress())
  287. }, 50))
  288. }
  289. }
  290.  
  291.  
  292. })();