119 lines
3.2 KiB
JavaScript
119 lines
3.2 KiB
JavaScript
(() => {
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
const canvas = document.getElementById("backgroundCanvas");
|
|
if (!canvas) return;
|
|
|
|
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
|
|
const ctx = canvas.getContext("2d", { alpha: true, desynchronized: true });
|
|
|
|
const isMobile = window.matchMedia("(max-width: 768px)").matches;
|
|
const DPR_CAP = Math.min(1.5, isMobile ? 1.0 : 1.25);
|
|
|
|
const getOrbCount = () => {
|
|
const baseCount = Math.floor((window.innerWidth * window.innerHeight) / 40000);
|
|
return Math.min(Math.max(baseCount, isMobile ? 15 : 25), isMobile ? 30 : 50);
|
|
};
|
|
|
|
const R_MIN = isMobile ? 3 : 4;
|
|
const R_MAX = isMobile ? 8 : 12;
|
|
const SPEED = isMobile ? 0.08 : 0.12;
|
|
|
|
const PINK = [245, 169, 184];
|
|
const BLUE = [91, 207, 250];
|
|
|
|
let w = 0, h = 0, dpr = 1;
|
|
let orbs = [];
|
|
let raf = 0;
|
|
let last = 0;
|
|
|
|
function resize() {
|
|
w = window.innerWidth;
|
|
h = window.innerHeight;
|
|
|
|
dpr = Math.min(DPR_CAP, window.devicePixelRatio || 1);
|
|
canvas.width = Math.floor(w * dpr);
|
|
canvas.height = Math.floor(h * dpr);
|
|
canvas.style.width = w + "px";
|
|
canvas.style.height = h + "px";
|
|
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
createOrbs();
|
|
last = 0;
|
|
}
|
|
|
|
function createOrbs() {
|
|
const count = getOrbCount();
|
|
orbs = new Array(count);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const r = Math.random() * (R_MAX - R_MIN) + R_MIN;
|
|
const color = Math.random() > 0.5 ? PINK : BLUE;
|
|
orbs[i] = {
|
|
x: Math.random() * w,
|
|
y: Math.random() * h,
|
|
r,
|
|
a: Math.random() * 0.15 + 0.4,
|
|
vx: (Math.random() - 0.5) * SPEED,
|
|
vy: (Math.random() - 0.5) * SPEED,
|
|
color: color
|
|
};
|
|
}
|
|
}
|
|
|
|
function step() {
|
|
ctx.clearRect(0, 0, w, h);
|
|
|
|
for (let i = 0; i < orbs.length; i++) {
|
|
const o = orbs[i];
|
|
o.x += o.vx;
|
|
o.y += o.vy;
|
|
|
|
const margin = o.r * 3;
|
|
if (o.x < -margin) o.x = w + margin;
|
|
else if (o.x > w + margin) o.x = -margin;
|
|
if (o.y < -margin) o.y = h + margin;
|
|
else if (o.y > h + margin) o.y = -margin;
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(o.x, o.y, o.r, 0, Math.PI * 2);
|
|
ctx.fillStyle = `rgba(${o.color[0]}, ${o.color[1]}, ${o.color[2]}, ${o.a})`;
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
function tick(now) {
|
|
if (document.hidden) {
|
|
raf = 0;
|
|
return;
|
|
}
|
|
|
|
if (!last) last = now;
|
|
if (now - last >= 16) {
|
|
last = now;
|
|
step();
|
|
}
|
|
|
|
raf = requestAnimationFrame(tick);
|
|
}
|
|
|
|
let resizeTimer;
|
|
window.addEventListener("resize", () => {
|
|
clearTimeout(resizeTimer);
|
|
resizeTimer = setTimeout(resize, 150);
|
|
}, { passive: true });
|
|
|
|
document.addEventListener("visibilitychange", () => {
|
|
if (document.hidden) {
|
|
cancelAnimationFrame(raf);
|
|
raf = 0;
|
|
} else if (!raf) {
|
|
last = 0;
|
|
raf = requestAnimationFrame(tick);
|
|
}
|
|
});
|
|
|
|
resize();
|
|
raf = requestAnimationFrame(tick);
|
|
});
|
|
})(); |