Files
wordpress-theme-aksrt/assets/js/loader.js
T

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