您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Visual indicator of page progress while scrolling
// ==UserScript== // @license MIT // @name Scroll Page Progress // @namespace http://tampermonkey.net/ // @version 1.8.3 // @description Visual indicator of page progress while scrolling // @author You // @match *://*/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // ==/UserScript== (function () { 'use strict'; const currentState = { deg: 0, progress: 0, zIndex: 0, movementIntervalId: null } let globalShadow let progressBar const createDiv = () => document.createElement('div') function insertCirculaProgressBarEl() { const shadowHost = createDiv() shadowHost.id = 'host-shwadow-circular-progress' const shadow = shadowHost.attachShadow({ mode: "closed" }); globalShadow = shadow const circularProgressBar = createDiv() progressBar = circularProgressBar const contentWrapper = createDiv() const closeOverlay = createDiv() const title = createDiv() const overlay = createDiv() const leftSide = createDiv() const rightSide = createDiv() circularProgressBar.classList.add('circular-progress-bar') title.classList.add('title'); closeOverlay.classList.add('close-overlay'); contentWrapper.classList.add('content-wrapper') overlay.classList.add('overlay'); leftSide.classList.add('left-side'); rightSide.classList.add('right-side'); title.innerText = '-%' // this is the only way to create a trusted HTML element if (window.trustedTypes) { closeOverlay.innerHTML = window.trustedTypes.defaultPolicy.createHTML('×') } else { closeOverlay.innerHTML = '×' } closeOverlay.addEventListener('click', function () { //circularProgressBar.style.display = 'none' const screenWidth = window.innerWidth; const elementoWidth = circularProgressBar.offsetWidth; // Calcular la nueva posición const newPosition = screenWidth - elementoWidth / 2; // Aplicar la nueva posición circularProgressBar.style.left = `${newPosition}px`; const topPosition = circularProgressBar.style.top const path = window.location.pathname; let savedPaths = JSON.parse(localStorage.getItem('not-allowed-paths')) || []; const existingEntryIndex = savedPaths.findIndex(entry => entry.path === path); const newEntry = { path: path, left: newPosition, top: topPosition || '0px' // Si el top no está definido, usa '0px' como valor por defecto }; if (existingEntryIndex !== -1) { // Si ya existe una entrada para la ruta, actualiza la posición savedPaths[existingEntryIndex] = newEntry; } else { // Si no existe, añade la nueva entrada savedPaths.push(newEntry); } console.log("savedPaths", savedPaths) // Guardar el array actualizado en localStorage localStorage.setItem('not-allowed-paths', JSON.stringify(savedPaths)); }); ;[title, overlay, leftSide, rightSide].forEach(childEl => contentWrapper.appendChild(childEl)) circularProgressBar.appendChild(closeOverlay) circularProgressBar.appendChild(contentWrapper) shadow.appendChild(circularProgressBar) document.body.appendChild(shadowHost) } function addCSS() { const styleSheet = document.createElement('style'); styleSheet.textContent = ` * { box-sizing: border-box; padding: 0; margin: 0; } .circular-progress-bar { --backgroundColor: #424242; --left-side-angle: 180deg; --barColor:orangered; width: 60px; height: 60px; color: #fff; border-radius: 50%; position: fixed; z-index: 2147483646; background: var(--backgroundColor); border: 5px solid white; box-shadow: 0 1px 1px hsl(0deg 0% 0% / 0.075), 0 2px 2px hsl(0deg 0% 0% / 0.075), 0 4px 4px hsl(0deg 0% 0% / 0.075), 0 8px 8px hsl(0deg 0% 0% / 0.075), 0 16px 16px hsl(0deg 0% 0% / 0.075); text-align: center; cursor: pointer; transition: opacity 0.2s ease; } .circular-progress-bar .overlay { width: 50%; height: 100%; position: absolute; top: 0; left: 0; background-color: var(--backgroundColor); transform-origin: right; transform: rotate(var(--overlay)); } .close-overlay { width: 20px; height: 20px; position: absolute; left: 100%; top: -30%; z-index: 2147483647; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: orangered; font-size: 19px; text-align: center; opacity: 0; } .close-overlay:hover { font-weight: bold; } .content-wrapper { overflow: hidden; height: 100%; width: 100%; border-radius: 50%; position: relative; } .circular-progress-bar:hover .close-overlay { opacity:1; } .circular-progress-bar .title { font-size: 15px; font-weight: bold; position:relative; height: 100%; display:flex; justify-content:center; align-items: center; z-index: 100; } .circular-progress-bar .left-side, .circular-progress-bar .right-side { width: 50%; height: 100%; position: absolute; top: 0; left: 0; border: 5px solid var(--barColor); border-radius: 100px 0px 0px 100px; border-right: 0; transform-origin: right; } .circular-progress-bar .left-side { transform: rotate(var(--left-side-angle)); } .circular-progress-bar .right-side { transform: rotate(var(--right-side-angle)); } ` globalShadow.appendChild(styleSheet) } function setAngle(deg) { const progressBar = globalShadow.querySelector('.circular-progress-bar') const leftSide = globalShadow.querySelector('.left-side') const rightSide = globalShadow.querySelector('.right-side') const overlay = globalShadow.querySelector('.circular-progress-bar .overlay') const zIndex = deg > 180 ? 100 : 0 const rightSideAngle = deg < 180 ? deg : 180 const leftSideAngle = deg const overlayAngle = deg < 180 ? 0 : deg - 180 const zIndexChangedToPositive = currentState.zIndex === 0 && zIndex === 100 if (deg > 180) { rightSide.style.zIndex = 2 leftSide.style.zIndex = 0 overlay.style.zIndex = 1 } else { rightSide.style.zIndex = 1 leftSide.style.zIndex = 0 overlay.style.zIndex = 2 } progressBar.style.setProperty('--overlay', `${overlayAngle}deg`); progressBar.style.setProperty('--right-side-angle', `${rightSideAngle}deg`); progressBar.style.setProperty('--left-side-angle', `${leftSideAngle}deg`); } function smoothProgressBar(targetProgress, duration) { if (currentState.movementIntervalId) { clearInterval(currentState.movementIntervalId); } let currentProgress = currentState.deg const increment = (targetProgress - currentProgress) / (duration / 10); currentState.movementIntervalId = setInterval(function () { currentProgress += increment; if ((increment > 0 && currentProgress >= targetProgress) || (increment < 0 && currentProgress <= targetProgress)) { currentProgress = targetProgress; clearInterval(currentState.movementIntervalId); } setAngle(currentProgress) }, 10); } function percentageToAngle(percentageNumber) { if (percentageNumber > 100) { return 360 } if (percentageNumber < 0) { return 0 } return (360 * percentageNumber) / 100 } function setPercentage(percentageNumber) { const angle = percentageToAngle(percentageNumber) smoothProgressBar(angle, 400) } function debounce(callback, wait) { let timerId; return (...args) => { clearTimeout(timerId); timerId = setTimeout(() => { callback(...args); }, wait); }; } function setEventListeners() { let offsetX = 0, offsetY = 0, isDragging = false; progressBar.addEventListener("mousedown", (e) => { e.preventDefault() offsetX = e.clientX - progressBar.offsetLeft; offsetY = e.clientY - progressBar.offsetTop; isDragging = true; progressBar.style.cursor = "grabbing"; progressBar.style.opacity = "0.3"; }); document.addEventListener("mousemove", (e) => { if (isDragging) { e.preventDefault(); const left = e.clientX - offsetX; const top = e.clientY - offsetY; progressBar.style.left = `${left}px`; progressBar.style.top = `${top}px`; savePosition(left, top); // Guardar la posición cada vez que se mueve } }); document.addEventListener("mouseup", () => { isDragging = false; progressBar.style.cursor = "grab"; progressBar.style.opacity = "1"; }); } function savePosition(left, top) { const position = { left, top }; localStorage.setItem("elementPosition", JSON.stringify(position)); } function loadPosition() { const currentPath = window.location.pathname; const savedPaths = JSON.parse(localStorage.getItem('not-allowed-paths')) || []; const entry = savedPaths.find(entry => entry.path === currentPath); if (entry) { console.log('exist entry') // Si existe una entrada para la ruta, aplica las posiciones guardadas progressBar.style.left = `${entry.left}px`; progressBar.style.top = `${entry.top}`; return; } const savedPosition = localStorage.getItem("elementPosition"); if (savedPosition) { const { left, top } = JSON.parse(savedPosition); progressBar.style.left = `${left}px`; progressBar.style.top = `${top}px`; } else { progressBar.style.right = `10px`; progressBar.style.top = `10px`; } } function getCurrentScrollProgress() { const winScroll = document.body.scrollTop || document.documentElement.scrollTop; const height = document.documentElement.scrollHeight - document.documentElement.clientHeight; const progress = (winScroll / height) * 100; return Math.trunc(progress); } function watchScroll() { const progressBarTitle = globalShadow.querySelector('.title') document.addEventListener('scroll', debounce(() => { setPercentage(getCurrentScrollProgress()) progressBarTitle.innerText = getCurrentScrollProgress() + '%' currentState.progress = getCurrentScrollProgress() currentState.deg = percentageToAngle(getCurrentScrollProgress()) }, 50)) } document.onreadystatechange = function () { if (document.readyState == "complete") { if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) { window.trustedTypes.createPolicy('default', { createHTML: (string, sink) => string }); } insertCirculaProgressBarEl() setEventListeners() addCSS() loadPosition() watchScroll() } } })();