Files
site-portfolio/public/js/modules/ui/page-mesh-bg.js
T

92 lines
3.1 KiB
JavaScript

import { prefersReducedMotion } from "../shared/media.js";
export function initPageMeshBackground() {
if (prefersReducedMotion()) return;
const root = document.documentElement;
let rafId = 0;
let showMarkers = false;
const markers = [];
function createMarker(label) {
const el = document.createElement("div");
el.textContent = label;
el.style.position = "fixed";
el.style.zIndex = "10000";
el.style.width = "18px";
el.style.height = "18px";
el.style.borderRadius = "999px";
el.style.background = "#000";
el.style.color = "#fff";
el.style.fontSize = "11px";
el.style.fontWeight = "700";
el.style.lineHeight = "18px";
el.style.textAlign = "center";
el.style.pointerEvents = "none";
el.style.boxShadow = "0 0 0 1px rgba(255,255,255,0.55)";
document.body.appendChild(el);
return el;
}
function setMarker(el, xPct, yPct) {
el.style.left = `calc(${xPct}% - 9px)`;
el.style.top = `calc(${yPct}% - 9px)`;
}
markers.push(createMarker("A"));
markers.push(createMarker("B"));
markers.push(createMarker("C"));
const markersToggle = document.querySelector("[data-mesh-markers-toggle]");
if (markersToggle instanceof HTMLInputElement) {
showMarkers = markersToggle.checked;
markersToggle.addEventListener("change", () => {
showMarkers = markersToggle.checked;
markers.forEach((m) => {
m.style.display = showMarkers ? "block" : "none";
});
});
markers.forEach((m) => {
m.style.display = showMarkers ? "block" : "none";
});
}
const hero = document.querySelector(".hero");
const spots = [
{ x: 2, y: 2 },
{ x: 50, y: 2 },
{ x: 98, y: 2 },
];
const update = () => {
rafId = 0;
const heroHeight = hero instanceof HTMLElement ? Math.max(hero.offsetHeight, 1) : Math.max(window.innerHeight, 1);
const heroProgress = Math.min(window.scrollY / heroHeight, 1);
const opacityMaxRaw = getComputedStyle(root).getPropertyValue("--mesh-opacity-max").trim();
const opacityMinRaw = getComputedStyle(root).getPropertyValue("--mesh-opacity-min").trim();
const opacityMax = Number.parseFloat(opacityMaxRaw);
const opacityMin = Number.parseFloat(opacityMinRaw);
const safeOpacityMax = Number.isFinite(opacityMax) ? opacityMax : 1;
const safeOpacityMin = Number.isFinite(opacityMin) ? opacityMin : 0;
const opacityNow = safeOpacityMax - (safeOpacityMax - safeOpacityMin) * heroProgress;
root.style.setProperty("--mesh-opacity-current", `${Math.max(opacityNow, safeOpacityMin).toFixed(3)}`);
spots.forEach((p, idx) => {
const key = ["a", "b", "c"][idx];
root.style.setProperty(`--mesh-${key}-x`, `${p.x.toFixed(2)}%`);
root.style.setProperty(`--mesh-${key}-y`, `${p.y.toFixed(2)}%`);
if (showMarkers) setMarker(markers[idx], p.x, p.y);
});
};
const onScroll = () => {
if (rafId) return;
rafId = window.requestAnimationFrame(update);
};
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll, { passive: true });
update();
}