// ==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()
}
}
})();