Scroll Page Progress

Visual indicator of page progress while scrolling

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

  1. // ==UserScript==
  2. // @license MIT
  3. // @name Scroll Page Progress
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.3
  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 title = document.createElement('div')
  35. const overlay = document.createElement('div')
  36. const leftSide = document.createElement('div')
  37. const rightSide = document.createElement('div')
  38.  
  39. globalShadow = shadow
  40.  
  41. circularProgressBar.classList.add('circular-progress-bar')
  42. title.classList.add('title');
  43. overlay.classList.add('overlay');
  44. leftSide.classList.add('left-side');
  45. rightSide.classList.add('right-side');
  46.  
  47. shadowHost.id = 'host-shwadow-circular-progress'
  48. title.innerText = '-%';
  49.  
  50. [title, overlay, leftSide, rightSide].forEach(childEl => circularProgressBar.appendChild(childEl))
  51. shadow.appendChild(circularProgressBar)
  52. document.body.appendChild(shadowHost)
  53. }
  54.  
  55. function addCSS() {
  56. const styleSheet = new CSSStyleSheet();
  57. styleSheet.replaceSync(`
  58. * {
  59. box-sizing: border-box;
  60. padding: 0;
  61. margin: 0;
  62. }
  63. .circular-progress-bar {
  64. --backgroundColor: #424242;
  65. --left-side-angle: 180deg;
  66. --barColor:orangered;
  67. width: 60px;
  68. height: 60px;
  69. color: #fff;
  70. border-radius: 50%;
  71. overflow: hidden;
  72. position: fixed;
  73. z-index: 999999999;
  74. background: var(--backgroundColor);
  75. border: 5px solid white;
  76. box-shadow:
  77. 0 1px 1px hsl(0deg 0% 0% / 0.075),
  78. 0 2px 2px hsl(0deg 0% 0% / 0.075),
  79. 0 4px 4px hsl(0deg 0% 0% / 0.075),
  80. 0 8px 8px hsl(0deg 0% 0% / 0.075),
  81. 0 16px 16px hsl(0deg 0% 0% / 0.075);
  82. text-align: center;
  83. }
  84. .circular-progress-bar .overlay {
  85. width: 50%;
  86. height: 100%;
  87. position: absolute;
  88. top: 0;
  89. left: 0;
  90. z-index: 1;
  91. background-color: var(--backgroundColor);
  92. }
  93. .circular-progress-bar .title {
  94. font-size: 15px;
  95. font-weight: bold;
  96. position:relative;
  97. height: 100%;
  98. display:flex;
  99. justify-content:center;
  100. align-items: center;
  101. z-index: 100;
  102. }
  103.  
  104. .circular-progress-bar .left-side,
  105. .circular-progress-bar .right-side {
  106. width: 50%;
  107. height: 100%;
  108. position: absolute;
  109. top: 0;
  110. left: 0;
  111. border: 5px solid var(--barColor);
  112. border-radius: 100px 0px 0px 100px;
  113. border-right: 0;
  114. transform-origin: right;
  115. }
  116. .circular-progress-bar .left-side {
  117. transform: rotate(var(--left-side-angle));
  118. z-index: var(--zindex);
  119. }
  120. .circular-progress-bar .right-side {
  121. transform: rotate(var(--right-side-angle));
  122. }
  123. `)
  124. globalShadow.adoptedStyleSheets = [styleSheet];
  125. }
  126.  
  127. insertCirculaProgressBarEl()
  128. addCSS()
  129.  
  130. const currentState = {
  131. deg: 0,
  132. progress: 0,
  133. zIndex: 0
  134. }
  135.  
  136. function setAngle(deg) {
  137. const progressBar = globalShadow.querySelector('.circular-progress-bar')
  138. const leftSide = globalShadow.querySelector('.left-side')
  139. const rightSide = globalShadow.querySelector('.right-side')
  140. leftSide.style.opacity = "0"
  141.  
  142. const zIndex = deg > 180 ? 100 : 0
  143. const rightSideAngle = deg < 180 ? deg : 180
  144. const leftSideAngle = deg
  145. const zIndexChangedToPositive = currentState.zIndex === 0 && zIndex === 100
  146. if (deg > 180) {
  147. leftSide.style.opacity = "1"
  148.  
  149. progressBar.style.setProperty('--left-side-angle', `${leftSideAngle}deg`);
  150. progressBar.style.setProperty('--zindex', zIndex);
  151. }
  152. progressBar.style.setProperty('--right-side-angle', `${rightSideAngle}deg`);
  153. currentState.deg = deg
  154. /*if (zIndex === 100 && zIndexChangedToPositive) {
  155. setUnsetOpacity()
  156. }*/
  157. }
  158. function percentageToAngle (percentageNumber) {
  159. if (percentageNumber > 100) {
  160. return 360
  161. }
  162. if (percentageNumber < 0) {
  163. return 0
  164. }
  165. return (360 * percentageNumber)/100
  166. }
  167.  
  168. function setPercentage(percentageNumber) {
  169. const angle = percentageToAngle(percentageNumber)
  170. setAngle(angle)
  171. }
  172.  
  173. function debounce(callback, wait) {
  174. let timerId;
  175. return (...args) => {
  176. clearTimeout(timerId);
  177. timerId = setTimeout(() => {
  178. callback(...args);
  179. }, wait);
  180. };
  181. }
  182. const progressBar = globalShadow.querySelector('.circular-progress-bar')
  183. const body = document.body;
  184.  
  185. let offsetX = 0, offsetY = 0, isDragging = false;
  186.  
  187. progressBar.addEventListener("mousedown", (e) => {
  188. e.preventDefault()
  189. offsetX = e.clientX - progressBar.offsetLeft;
  190. offsetY = e.clientY - progressBar.offsetTop;
  191. isDragging = true;
  192. progressBar.style.cursor = "grabbing";
  193. });
  194.  
  195. document.addEventListener("mousemove", (e) => {
  196. if (isDragging) {
  197. e.preventDefault();
  198. const left = e.clientX - offsetX;
  199. const top = e.clientY - offsetY;
  200. progressBar.style.left = `${left}px`;
  201. progressBar.style.top = `${top}px`;
  202. savePosition(left, top); // Guardar la posición cada vez que se mueve
  203. }
  204. });
  205.  
  206. document.addEventListener("mouseup", () => {
  207. isDragging = false;
  208. progressBar.style.cursor = "grab";
  209. });
  210.  
  211. function savePosition(left, top) {
  212. const position = { left, top };
  213. localStorage.setItem("elementPosition", JSON.stringify(position));
  214. }
  215.  
  216. function loadPosition() {
  217. const savedPosition = localStorage.getItem("elementPosition");
  218. if (savedPosition) {
  219. const { left, top } = JSON.parse(savedPosition);
  220. progressBar.style.left = `${left}px`;
  221. progressBar.style.top = `${top}px`;
  222. } else {
  223. progressBar.style.right = `10px`;
  224. progressBar.style.top = `10px`;
  225. }
  226. }
  227.  
  228. function getCurrentScrollProgress() {
  229. const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
  230. const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
  231. const progress = (winScroll / height) * 100;
  232. return Math.trunc(progress);
  233. }
  234. const progressBarTitle = globalShadow.querySelector('.title')
  235. document.onreadystatechange = function () {
  236. if (document.readyState == "complete") {
  237. loadPosition()
  238. document.addEventListener('scroll', debounce(() => {
  239. setPercentage(getCurrentScrollProgress())
  240. progressBarTitle.innerText = getCurrentScrollProgress() + '%'
  241. console.log(progressBarTitle, getCurrentScrollProgress())
  242. currentState.progress = getCurrentScrollProgress()
  243. currentState.deg = percentageToAngle(getCurrentScrollProgress())
  244. }, 50))
  245. }
  246. }
  247.  
  248.  
  249. })();