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