PageSourceSearch

https://dwightgingrich.com/js/post-progress-bar.js

js dwightgingrich.com collected 2026-09-27 03:49:19 UTC 5,945 bytes, 149 lines download raw bytes

1(function () {
2    if (window.__progressBarGlobalInit) return;
3    window.__progressBarGlobalInit = true;
4
5    let cleanup = null;
6
7    function initProgressBar() {
8        // Cleanup previous instance
9        if (cleanup) {
10            cleanup();
11            cleanup = null;
12        }
13
14        // Use requestAnimationFrame to batch DOM reads
15        requestAnimationFrame(function () {
16            // Only show progress bar on individual post pages, not homepage or category pages
17            const postContainer = document.querySelector('article.post-content, main > article:only-of-type');
18            if (!postContainer) return;
19
20            // Additional check: don't show on homepage or pages with multiple articles
21            const allArticles = document.querySelectorAll('article');
22            if (allArticles.length > 1) return;
23
24            // Create and insert the progress bar after the navbar
25            const navbar = document.querySelector('.navbar');
26            let progressBar = document.getElementById('scrollProgressBar');
27            if (!progressBar) {
28                progressBar = document.createElement('div');
29                progressBar.id = 'scrollProgressBar';
30                progressBar.style.cssText =
31                    'position: fixed; ' +
32                    'top: 0; ' +
33                    'inset-inline-start: 0; ' +
34                    'width: 0%; ' +
35                    'z-index: var(--z-index-progress-bar);';
36                if (navbar && navbar.nextSibling) {
37                    navbar.parentNode.insertBefore(progressBar, navbar.nextSibling);
38                } else {
39                    document.body.insertBefore(progressBar, document.body.firstChild);
40                }
41            } else {
42                // Reset width for new page
43                progressBar.style.width = '0%';
44            }
45
46            let navbarHeight = 0;
47            let articleTop = 0;
48            let articleHeight = 0;
49            let windowHeight = 0;
50            let totalScrollable = 0;
51            let navbarIsFixed = false;
52            let rafId = null;
53            let scrollPending = false;
54
55            function updateLayoutMeasurements() {
56                requestAnimationFrame(function () {
57                    if (navbar) {
58                        navbarHeight = navbar.offsetHeight;
59                        navbarIsFixed = navbar.classList.contains('fixed');
60                        if (!navbarIsFixed) {
61                            const style = window.getComputedStyle(navbar);
62                            navbarIsFixed = style.position === 'fixed';
63                        }
64                    }
65                    articleTop = postContainer.offsetTop;
66                    articleHeight = postContainer.scrollHeight;
67                    windowHeight = window.innerHeight;
68                    const effectiveViewportHeight = navbarIsFixed ? windowHeight - navbarHeight : windowHeight;
69                    totalScrollable = articleHeight - effectiveViewportHeight / 2;
70                    setProgressBarPosition();
71                });
72            }
73
74            function setProgressBarPosition() {
75                if (!navbar || !progressBar) return;
76                const newTop = navbarIsFixed ? navbarHeight : 0;
77                if (progressBar.style.top !== newTop + 'px') {
78                    progressBar.style.top = newTop + 'px';
79                }
80            }
81
82            function updateProgressBar() {
83                if (!progressBar || scrollPending) return;
84                scrollPending = true;
85
86                rafId = requestAnimationFrame(function () {
87                    const scrollTop = window.scrollY || document.documentElement.scrollTop;
88                    const scrolled = scrollTop - articleTop;
89                    let progress = (scrolled / totalScrollable) * 100;
90                    progress = Math.max(0, Math.min(progress, 100));
91                    progressBar.style.width = progress + '%';
92                    scrollPending = false;
93                });
94            }
95
96            let resizeTimeout;
97            function handleResize() {
98                clearTimeout(resizeTimeout);
99                resizeTimeout = setTimeout(function() {
100                    updateLayoutMeasurements();
101                    updateProgressBar();
102                }, 150);
103            }
104
105            function handleScroll() {
106                updateProgressBar();
107            }
108
109            // Initial measurements and setup
110            updateLayoutMeasurements();
111            updateProgressBar();
112
113            // Add event listeners
114            window.addEventListener('resize', handleResize);
115            window.addEventListener('scroll', handleScroll, { passive: true });
116
117            // Recalculate when article size changes
118            const resizeObserver = new ResizeObserver(function() {
119                clearTimeout(resizeTimeout);
120                resizeTimeout = setTimeout(function() {
121                    updateLayoutMeasurements();
122                    updateProgressBar();
123                }, 100);
124            });
125            resizeObserver.observe(postContainer);
126
127            // Store cleanup function
128            cleanup = function() {
129                window.removeEventListener('resize', handleResize);
130                window.removeEventListener('scroll', handleScroll);
131                if (rafId) cancelAnimationFrame(rafId);
132                clearTimeout(resizeTimeout);
133                resizeObserver.disconnect();
134                // Hide progress bar when leaving page
135                if (progressBar) progressBar.style.width = '0%';
136            };
137        });
138    }
139
140    // Run on initial load
141    if (document.readyState === 'loading') {
142        document.addEventListener('DOMContentLoaded', initProgressBar);
143    } else {
144        initProgressBar();
145    }
146
147    // Re-run on Turbo navigation
148    document.addEventListener('turbo:load', initProgressBar);
149})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.