173 lines
4.4 KiB
JavaScript
173 lines
4.4 KiB
JavaScript
/**
|
|
* 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();
|
|
}
|
|
})();
|