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.5
  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 = document.createElement('style');
  57. styleSheet.textContent = `
  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. cursor: pointer;
  84. transition: opacity 0.2s ease;
  85. }
  86. .circular-progress-bar .overlay {
  87. width: 50%;
  88. height: 100%;
  89. position: absolute;
  90. top: 0;
  91. left: 0;
  92. z-index: 1;
  93. background-color: var(--backgroundColor);
  94. }
  95. .circular-progress-bar .title {
  96. font-size: 15px;
  97. font-weight: bold;
  98. position:relative;
  99. height: 100%;
  100. display:flex;
  101. justify-content:center;
  102. align-items: center;
  103. z-index: 100;
  104. }
  105.  
  106. .circular-progress-bar .left-side,
  107. .circular-progress-bar .right-side {
  108. width: 50%;
  109. height: 100%;
  110. position: absolute;
  111. top: 0;
  112. left: 0;
  113. border: 5px solid var(--barColor);
  114. border-radius: 100px 0px 0px 100px;
  115. border-right: 0;
  116. transform-origin: right;
  117. }
  118. .circular-progress-bar .left-side {
  119. transform: rotate(var(--left-side-angle));
  120. z-index: var(--zindex);
  121. }
  122. .circular-progress-bar .right-side {
  123. transform: rotate(var(--right-side-angle));
  124. }
  125. `
  126. globalShadow.appendChild(styleSheet)
  127. }
  128.  
  129. insertCirculaProgressBarEl()
  130. addCSS()
  131.  
  132. const currentState = {
  133. deg: 0,
  134. progress: 0,
  135. zIndex: 0
  136. }
  137.  
  138. function setAngle(deg) {
  139. const progressBar = globalShadow.querySelector('.circular-progress-bar')
  140. const leftSide = globalShadow.querySelector('.left-side')
  141. const rightSide = globalShadow.querySelector('.right-side')
  142. leftSide.style.opacity = "0"
  143.  
  144. const zIndex = deg > 180 ? 100 : 0
  145. const rightSideAngle = deg < 180 ? deg : 180
  146. const leftSideAngle = deg
  147. const zIndexChangedToPositive = currentState.zIndex === 0 && zIndex === 100
  148. if (deg > 180) {
  149. leftSide.style.opacity = "1"
  150.  
  151. progressBar.style.setProperty('--left-side-angle', `${leftSideAngle}deg`);
  152. progressBar.style.setProperty('--zindex', zIndex);
  153. }
  154. progressBar.style.setProperty('--right-side-angle', `${rightSideAngle}deg`);
  155. currentState.deg = deg
  156. /*if (zIndex === 100 && zIndexChangedToPositive) {
  157. setUnsetOpacity()
  158. }*/
  159. }
  160. function percentageToAngle (percentageNumber) {
  161. if (percentageNumber > 100) {
  162. return 360
  163. }
  164. if (percentageNumber < 0) {
  165. return 0
  166. }
  167. return (360 * percentageNumber)/100
  168. }
  169.  
  170. function setPercentage(percentageNumber) {
  171. const angle = percentageToAngle(percentageNumber)
  172. setAngle(angle)
  173. }
  174.  
  175. function debounce(callback, wait) {
  176. let timerId;
  177. return (...args) => {
  178. clearTimeout(timerId);
  179. timerId = setTimeout(() => {
  180. callback(...args);
  181. }, wait);
  182. };
  183. }
  184. const progressBar = globalShadow.querySelector('.circular-progress-bar')
  185. const body = document.body;
  186.  
  187. let offsetX = 0, offsetY = 0, isDragging = false;
  188.  
  189. progressBar.addEventListener("mousedown", (e) => {
  190. e.preventDefault()
  191. offsetX = e.clientX - progressBar.offsetLeft;
  192. offsetY = e.clientY - progressBar.offsetTop;
  193. isDragging = true;
  194. progressBar.style.cursor = "grabbing";
  195. progressBar.style.opacity = "0.3";
  196. });
  197.  
  198. document.addEventListener("mousemove", (e) => {
  199. if (isDragging) {
  200. e.preventDefault();
  201. const left = e.clientX - offsetX;
  202. const top = e.clientY - offsetY;
  203. progressBar.style.left = `${left}px`;
  204. progressBar.style.top = `${top}px`;
  205. savePosition(left, top); // Guardar la posición cada vez que se mueve
  206. }
  207. });
  208.  
  209. document.addEventListener("mouseup", () => {
  210. isDragging = false;
  211. progressBar.style.cursor = "grab";
  212. progressBar.style.opacity = "1";
  213. });
  214.  
  215. function savePosition(left, top) {
  216. const position = { left, top };
  217. localStorage.setItem("elementPosition", JSON.stringify(position));
  218. }
  219.  
  220. function loadPosition() {
  221. const savedPosition = localStorage.getItem("elementPosition");
  222. if (savedPosition) {
  223. const { left, top } = JSON.parse(savedPosition);
  224. progressBar.style.left = `${left}px`;
  225. progressBar.style.top = `${top}px`;
  226. } else {
  227. progressBar.style.right = `10px`;
  228. progressBar.style.top = `10px`;
  229. }
  230. }
  231.  
  232. function getCurrentScrollProgress() {
  233. const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
  234. const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
  235. const progress = (winScroll / height) * 100;
  236. return Math.trunc(progress);
  237. }
  238. const progressBarTitle = globalShadow.querySelector('.title')
  239. document.onreadystatechange = function () {
  240. if (document.readyState == "complete") {
  241. loadPosition()
  242. document.addEventListener('scroll', debounce(() => {
  243. setPercentage(getCurrentScrollProgress())
  244. progressBarTitle.innerText = getCurrentScrollProgress() + '%'
  245. console.log(progressBarTitle, getCurrentScrollProgress())
  246. currentState.progress = getCurrentScrollProgress()
  247. currentState.deg = percentageToAngle(getCurrentScrollProgress())
  248. }, 50))
  249. }
  250. }
  251.  
  252.  
  253. })();