/** * AKSRT Page Loader * * Lightweight top progress bar for page transitions and AJAX requests. * No external dependencies — pure vanilla JS. * * @package AKSRT */ (function () { 'use strict'; var STYLE_ID = 'aksrt-loader-style'; var BAR_ID = 'aksrt-loader-bar'; var CSS = '#' + BAR_ID + '{' + 'position:fixed;top:0;left:0;width:0;height:3px;z-index:99999;' + 'background:linear-gradient(90deg,#1890ff,#36cfc9);' + 'transition:width 0.3s ease,opacity 0.3s ease;' + 'pointer-events:none;opacity:0;' + '}' + '#' + BAR_ID + '.is-active{opacity:1;}' + '#' + BAR_ID + '.is-done{' + 'width:100%!important;opacity:0;' + '}'; var bar = null; var timer = null; var progress = 0; function injectStyle() { if (document.getElementById(STYLE_ID)) return; var style = document.createElement('style'); style.id = STYLE_ID; style.textContent = CSS; document.head.appendChild(style); } function createBar() { if (bar) return; bar = document.createElement('div'); bar.id = BAR_ID; document.body.appendChild(bar); } function start() { if (!bar) createBar(); clearTimeout(timer); bar.classList.remove('is-done'); bar.classList.add('is-active'); progress = 0; bar.style.width = '0%'; trickle(); } function trickle() { if (progress >= 90) return; var increment = progress < 30 ? 8 : progress < 60 ? 4 : 2; progress = Math.min(90, progress + increment * (0.8 + Math.random() * 0.4)); bar.style.width = progress + '%'; timer = setTimeout(trickle, 200 + Math.random() * 300); } function done() { if (!bar) return; clearTimeout(timer); bar.style.width = '100%'; bar.classList.add('is-done'); setTimeout(function () { bar.classList.remove('is-active', 'is-done'); bar.style.width = '0%'; progress = 0; }, 400); } // Intercept link clicks for same-origin page transitions function onLinkClick(e) { if (e.defaultPrevented) return; if (e.button !== 0) return; // left click only if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; var link = e.target.closest('a'); if (!link || !link.href) return; if (link.target === '_blank') return; if (link.hasAttribute('download')) return; if (link.href.indexOf('#') === 0) return; // Same-origin only try { var url = new URL(link.href); if (url.origin !== window.location.origin) return; if (url.protocol !== "http:" && url.protocol !== "https:") return; if (url.pathname === window.location.pathname && url.search === window.location.search) return; } catch (ex) { return; } start(); } // Intercept form submissions function onFormSubmit(e) { var form = e.target; if (!form || form.tagName !== 'FORM') return; if (form.target === '_blank') return; start(); } // Hook into AJAX (fetch) if available function hookFetch() { if (!window.fetch) return; var origFetch = window.fetch; window.fetch = function () { start(); return origFetch.apply(this, arguments).then(function (response) { done(); return response; }).catch(function (err) { done(); throw err; }); }; } // Hook into XMLHttpRequest function hookXHR() { if (!window.XMLHttpRequest) return; var origOpen = XMLHttpRequest.prototype.open; var origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function () { this._aksrtLoader = true; return origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function () { if (this._aksrtLoader) { start(); var self = this; this.addEventListener('loadend', function () { done(); }); } return origSend.apply(this, arguments); }; } function init() { injectStyle(); createBar(); document.addEventListener('click', onLinkClick, true); document.addEventListener('submit', onFormSubmit, true); // Done on page load (handles bfcache) window.addEventListener('pageshow', function (e) { if (e.persisted) done(); }); window.addEventListener('load', done); hookFetch(); hookXHR(); } // Public API window.AksrtLoader = { start: start, done: done }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();