153 lines
4.2 KiB
JavaScript
153 lines
4.2 KiB
JavaScript
/**
|
|
* Блик по буквам логотипа в navbar (тёмная тема) — тот же drawShimmer, что в wesp-logo-loader.
|
|
*/
|
|
(function () {
|
|
const LOGO_SRC = "/static/logo2.png";
|
|
const CYCLE_MS = 3000;
|
|
const SHIMMER_MS = 1000;
|
|
const START_DELAY_MS = 350;
|
|
|
|
const mounts = new WeakMap();
|
|
|
|
function isDarkTheme() {
|
|
return document.documentElement.getAttribute("data-theme") === "dark";
|
|
}
|
|
|
|
function prefersReducedMotion() {
|
|
return globalThis.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false;
|
|
}
|
|
|
|
function loadImage(src) {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
img.onload = () => resolve(img);
|
|
img.onerror = () => reject(new Error(`Failed to load: ${src}`));
|
|
img.src = src;
|
|
});
|
|
}
|
|
|
|
function disposeMount(link) {
|
|
const state = mounts.get(link);
|
|
if (!state) return;
|
|
state.disposed = true;
|
|
if (state.rafId) cancelAnimationFrame(state.rafId);
|
|
state.canvas?.remove();
|
|
link.classList.remove("nav-logo-link--canvas-shimmer");
|
|
mounts.delete(link);
|
|
}
|
|
|
|
async function mountCanvasShimmer(link) {
|
|
if (!isDarkTheme() || prefersReducedMotion()) return;
|
|
disposeMount(link);
|
|
|
|
let logoModule;
|
|
try {
|
|
logoModule = await import("/static/js/wesp-logo-loader.js?v=2");
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
const { createNavLogoShimmerBuffer, drawShimmer } = logoModule;
|
|
let img;
|
|
try {
|
|
img = await loadImage(LOGO_SRC);
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
const canvas = document.createElement("canvas");
|
|
canvas.className = "nav-logo__shimmer-canvas";
|
|
canvas.setAttribute("aria-hidden", "true");
|
|
link.classList.add("nav-logo-link--canvas-shimmer");
|
|
link.appendChild(canvas);
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
const state = {
|
|
disposed: false,
|
|
rafId: 0,
|
|
canvas,
|
|
logoCanvas: null,
|
|
layout: null,
|
|
startAt: performance.now() + START_DELAY_MS,
|
|
lastW: 0,
|
|
lastH: 0,
|
|
};
|
|
mounts.set(link, state);
|
|
|
|
function rebuildBuffer() {
|
|
const w = Math.max(1, Math.round(link.clientWidth));
|
|
const h = Math.max(1, Math.round(link.clientHeight));
|
|
if (w === state.lastW && h === state.lastH && state.logoCanvas) return;
|
|
|
|
state.lastW = w;
|
|
state.lastH = h;
|
|
const dpr = Math.min(globalThis.devicePixelRatio || 1, 2);
|
|
canvas.width = Math.round(w * dpr);
|
|
canvas.height = Math.round(h * dpr);
|
|
canvas.style.width = `${w}px`;
|
|
canvas.style.height = `${h}px`;
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
const built = createNavLogoShimmerBuffer(img, w, h, [255, 255, 255]);
|
|
state.logoCanvas = built.logoCanvas;
|
|
state.layout = built.layout;
|
|
}
|
|
|
|
function tick(now) {
|
|
if (state.disposed) return;
|
|
if (!isDarkTheme()) {
|
|
disposeMount(link);
|
|
return;
|
|
}
|
|
|
|
rebuildBuffer();
|
|
|
|
const w = state.lastW;
|
|
const h = state.lastH;
|
|
ctx.clearRect(0, 0, w, h);
|
|
|
|
const elapsed = now - state.startAt;
|
|
if (elapsed >= 0 && state.logoCanvas && state.layout) {
|
|
const cycle = elapsed % CYCLE_MS;
|
|
if (cycle < SHIMMER_MS) {
|
|
const shimmerT = cycle / SHIMMER_MS;
|
|
ctx.drawImage(state.logoCanvas, 0, 0, w, h);
|
|
drawShimmer(ctx, state.layout, state.logoCanvas, shimmerT, false);
|
|
}
|
|
}
|
|
|
|
state.rafId = requestAnimationFrame(tick);
|
|
}
|
|
|
|
rebuildBuffer();
|
|
state.rafId = requestAnimationFrame(tick);
|
|
}
|
|
|
|
function init() {
|
|
if (prefersReducedMotion()) return;
|
|
|
|
document
|
|
.querySelectorAll(".mobile-navbar .nav-logo-link, .mobile-brand .nav-logo-link")
|
|
.forEach((link) => {
|
|
if (isDarkTheme()) {
|
|
mountCanvasShimmer(link);
|
|
} else {
|
|
disposeMount(link);
|
|
}
|
|
});
|
|
}
|
|
|
|
function onThemeChange() {
|
|
init();
|
|
}
|
|
|
|
window.WespNavLogoShimmer = { init, disposeMount };
|
|
|
|
const themeObserver = new MutationObserver(onThemeChange);
|
|
themeObserver.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ["data-theme"],
|
|
});
|
|
|
|
/* init вызывается из wesp-zootech-nav.js после вставки navbar и из wesp-page-enter после enter-done */
|
|
})();
|