136 lines
3.6 KiB
JavaScript
136 lines
3.6 KiB
JavaScript
/**
|
|
* Мягкая поэтапная анимация zootech-страниц: navbar → main → блоки.
|
|
* html.wesp-page-enter-pending ставится inline в <head> до первого paint.
|
|
*/
|
|
(function () {
|
|
const MAX_ITEMS = 8;
|
|
let held = false;
|
|
let revealed = false;
|
|
|
|
function prefersReducedMotion() {
|
|
return globalThis.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false;
|
|
}
|
|
|
|
function isGridListPage() {
|
|
const body = document.body;
|
|
if (!body) return false;
|
|
return (
|
|
body.classList.contains("components-page") ||
|
|
body.classList.contains("feed-dispensers-page") ||
|
|
body.classList.contains("reports-page")
|
|
);
|
|
}
|
|
|
|
function markEnterItems() {
|
|
const html = document.documentElement;
|
|
if (isGridListPage()) {
|
|
html.classList.add("wesp-page-enter-grid-page");
|
|
return;
|
|
}
|
|
html.classList.remove("wesp-page-enter-grid-page");
|
|
|
|
const root = document.querySelector(".wesp-page-main");
|
|
if (!root) return;
|
|
|
|
let items = Array.from(root.querySelectorAll("[data-wesp-enter]"));
|
|
if (!items.length) {
|
|
items = Array.from(root.children).filter((el) => el.nodeType === 1);
|
|
}
|
|
|
|
items.slice(0, MAX_ITEMS).forEach((el, i) => {
|
|
el.classList.add("wesp-page-enter-item");
|
|
el.style.setProperty("--wesp-enter-i", String(i));
|
|
});
|
|
|
|
document.querySelectorAll("[data-wesp-enter].app-footer, footer[data-wesp-enter]").forEach((el, i) => {
|
|
if (el.classList.contains("wesp-page-enter-item")) return;
|
|
el.classList.add("wesp-page-enter-item");
|
|
el.style.setProperty("--wesp-enter-i", String(items.length + i));
|
|
});
|
|
}
|
|
|
|
function finalizeReveal() {
|
|
if (revealed) return;
|
|
revealed = true;
|
|
const html = document.documentElement;
|
|
html.classList.remove("wesp-page-enter-pending");
|
|
html.classList.add("wesp-page-enter-ready");
|
|
|
|
let settled = false;
|
|
let fallbackTimer = 0;
|
|
|
|
const settleDone = () => {
|
|
if (settled || !html.classList.contains("wesp-page-enter-ready")) return;
|
|
settled = true;
|
|
if (fallbackTimer) window.clearTimeout(fallbackTimer);
|
|
|
|
requestAnimationFrame(() => {
|
|
html.classList.remove("wesp-page-enter-ready");
|
|
html.classList.add("wesp-page-enter-done");
|
|
requestAnimationFrame(() => {
|
|
globalThis.WespNavLogoShimmer?.init?.();
|
|
});
|
|
});
|
|
};
|
|
|
|
const items = document.querySelectorAll(".wesp-page-enter-item");
|
|
let pending = items.length;
|
|
if (pending === 0) {
|
|
settleDone();
|
|
return;
|
|
}
|
|
|
|
const onAnimEnd = (event) => {
|
|
if (event.animationName !== "wesp-enter-fade") return;
|
|
pending -= 1;
|
|
if (pending <= 0) settleDone();
|
|
};
|
|
|
|
items.forEach((el) => {
|
|
el.addEventListener("animationend", onAnimEnd, { once: true });
|
|
});
|
|
|
|
const fallbackMs =
|
|
80 + (MAX_ITEMS - 1) * 55 + 350 + 80;
|
|
fallbackTimer = window.setTimeout(settleDone, fallbackMs);
|
|
}
|
|
|
|
function revealPageMain() {
|
|
if (revealed || held) return;
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
if (!held && !revealed) finalizeReveal();
|
|
});
|
|
});
|
|
}
|
|
|
|
function holdPageMain() {
|
|
held = true;
|
|
}
|
|
|
|
function releaseAndReveal() {
|
|
held = false;
|
|
if (!revealed) revealPageMain();
|
|
}
|
|
|
|
function init() {
|
|
markEnterItems();
|
|
|
|
if (prefersReducedMotion()) {
|
|
finalizeReveal();
|
|
document.documentElement.classList.add("wesp-page-enter-done");
|
|
return;
|
|
}
|
|
|
|
if (!held) {
|
|
revealPageMain();
|
|
}
|
|
}
|
|
|
|
window.WespPageEnter = {
|
|
init,
|
|
hold: holdPageMain,
|
|
reveal: releaseAndReveal,
|
|
};
|
|
})();
|