Scroll Page Progress

Visual indicator of page progress while scrolling

Versión del día 09/08/2024. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @license MIT
// @name         Scroll Page Progress
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  Visual indicator of page progress while scrolling
// @author       You
// @match        *://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    let globalShadow

    function once(fn, context) {
        var result;

        return function() {
            if(fn) {
                result = fn.apply(context || this, arguments);
                fn = null;
            }

            return result;
        };
    }

    function insertCirculaProgressBarEl() {
        const shadowHost = document.createElement('div')
        const shadow = shadowHost.attachShadow({ mode: "closed" });
        const circularProgressBar = document.createElement('div')
        const closeOverlay = document.createElement('div')
        const title = document.createElement('div')
        const overlay = document.createElement('div')
        const leftSide = document.createElement('div')
        const rightSide = document.createElement('div')

        globalShadow = shadow

        circularProgressBar.classList.add('circular-progress-bar')
        title.classList.add('title');
        closeOverlay.classList.add('close-overlay');
        overlay.classList.add('overlay');
        leftSide.classList.add('left-side');
        rightSide.classList.add('right-side');

        shadowHost.id = 'host-shwadow-circular-progress'
        title.innerText = '-%'
        closeOverlay.innerHTML = '×'
      
      	closeOverlay.addEventListener('click', function() {
          	circularProgressBar.style.display = 'none'
            const path = window.location.pathname;
            let savedPaths = JSON.parse(localStorage.getItem('not-allowed-paths')) || [];
						console.log(path, savedPaths)
            if (!savedPaths.includes(path)) {
                savedPaths.push(path);
                localStorage.setItem('not-allowed-paths', JSON.stringify(savedPaths));
            }
        });

        ;[closeOverlay, title, overlay, leftSide, rightSide].forEach(childEl => circularProgressBar.appendChild(childEl))
        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%;
          overflow: hidden;
          position: fixed;
          z-index: 999999999;
          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;
          z-index: 1;
          background-color: var(--backgroundColor);
        }
        .close-overlay {
            width: 100%;
            height: 100%;
            position: absolute;
            z-index: 101;
            display: flex;
            justify-content: center;
            align-items: center;
            /* overflow: hidden; */
            border-radius: 50%;
            background: red;
            font-size: 25px;
            text-align: center;
            /* line-height: 82px; */
            opacity: 0;
        }
        
        .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));
          z-index: var(--zindex);
        }
        .circular-progress-bar .right-side {
          transform: rotate(var(--right-side-angle));
        }
  `
        globalShadow.appendChild(styleSheet)
    }

    insertCirculaProgressBarEl()
    addCSS()

    const currentState = {
        deg: 0,
        progress: 0,
        zIndex: 0
    }

    function setAngle(deg) {
        const progressBar = globalShadow.querySelector('.circular-progress-bar')
        const leftSide = globalShadow.querySelector('.left-side')
        const rightSide = globalShadow.querySelector('.right-side')
        leftSide.style.opacity = "0"

        const zIndex = deg > 180 ? 100 : 0
        const rightSideAngle = deg < 180 ? deg : 180
        const leftSideAngle = deg
        const zIndexChangedToPositive = currentState.zIndex === 0 && zIndex === 100
        if (deg > 180) {
            leftSide.style.opacity = "1"

            progressBar.style.setProperty('--left-side-angle', `${leftSideAngle}deg`);
            progressBar.style.setProperty('--zindex', zIndex);
        }
        progressBar.style.setProperty('--right-side-angle', `${rightSideAngle}deg`);
        currentState.deg = deg
        /*if (zIndex === 100 && zIndexChangedToPositive) {
         setUnsetOpacity()
        }*/
    }
    function percentageToAngle (percentageNumber) {
        if (percentageNumber > 100) {
            return 360
        }
        if (percentageNumber < 0) {
            return 0
        }
        return (360 * percentageNumber)/100
    }

    function setPercentage(percentageNumber) {
        const angle = percentageToAngle(percentageNumber)
        setAngle(angle)
    }

    function debounce(callback, wait) {
        let timerId;
        return (...args) => {
            clearTimeout(timerId);
            timerId = setTimeout(() => {
                callback(...args);
            }, wait);
        };
    }
    const progressBar = globalShadow.querySelector('.circular-progress-bar')
    const body = document.body;

    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 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);
    }
    const progressBarTitle = globalShadow.querySelector('.title')
    document.onreadystatechange = function () {
        if (document.readyState == "complete") {
          const currentPath = window.location.pathname;
          const savedPaths = JSON.parse(localStorage.getItem('not-allowed-paths')) || [];

          if (savedPaths.includes(currentPath)) {
            progressBar.style.display = 'none';
            return;
          }
          loadPosition()
          document.addEventListener('scroll', debounce(() => {
            setPercentage(getCurrentScrollProgress())
            progressBarTitle.innerText = getCurrentScrollProgress() + '%'
            console.log(progressBarTitle, getCurrentScrollProgress())
            currentState.progress = getCurrentScrollProgress()
            currentState.deg = percentageToAngle(getCurrentScrollProgress())
          }, 50))
        }
    }


})();