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