Il canvas particelle era alto quanto tutto il documento e ridisegnava ogni frame; ora resta sul viewport e si ferma durante lo scroll. Co-authored-by: Cursor <cursoragent@cursor.com>
192 lines
4.7 KiB
JavaScript
192 lines
4.7 KiB
JavaScript
/**
|
||
* Rete di particelle + linee (canvas #particleCanvas).
|
||
* Limitato al viewport (non all’altezza documento): evita canvas giganti che
|
||
* bloccano lo scroll sulla home lunga.
|
||
*/
|
||
(function () {
|
||
if (typeof window === 'undefined') return;
|
||
|
||
var canvas = document.getElementById('particleCanvas');
|
||
if (!canvas) return;
|
||
var ctx = canvas.getContext('2d', { alpha: true });
|
||
if (!ctx) return;
|
||
|
||
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||
var particles = [];
|
||
var mouse = { x: null, y: null, radius: 150 };
|
||
var raf = 0;
|
||
var running = false;
|
||
var cachedW = 0;
|
||
var cachedH = 0;
|
||
var scrollPauseTimer = 0;
|
||
|
||
function viewportSize() {
|
||
var w = Math.max(1, Math.floor(window.innerWidth || document.documentElement.clientWidth || 1));
|
||
var h = Math.max(1, Math.floor(window.innerHeight || document.documentElement.clientHeight || 1));
|
||
return { w: w, h: h };
|
||
}
|
||
|
||
function Particle(w, h) {
|
||
this.x = Math.random() * w;
|
||
this.y = Math.random() * h;
|
||
this.size = Math.random() * 2 + 1;
|
||
this.speedX = (Math.random() - 0.5) * 1.2;
|
||
this.speedY = (Math.random() - 0.5) * 1.2;
|
||
}
|
||
|
||
Particle.prototype.draw = function () {
|
||
ctx.fillStyle = 'rgba(148, 163, 184, 0.85)';
|
||
ctx.beginPath();
|
||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
};
|
||
|
||
Particle.prototype.update = function (w, h) {
|
||
this.x += this.speedX;
|
||
this.y += this.speedY;
|
||
|
||
if (this.x > w || this.x < 0) this.speedX *= -1;
|
||
if (this.y > h || this.y < 0) this.speedY *= -1;
|
||
|
||
if (mouse.x == null || mouse.y == null) return;
|
||
|
||
var dx = mouse.x - this.x;
|
||
var dy = mouse.y - this.y;
|
||
var distance = Math.sqrt(dx * dx + dy * dy);
|
||
|
||
if (distance > 1 && distance < mouse.radius) {
|
||
var fx = dx / distance;
|
||
var fy = dy / distance;
|
||
var force = (mouse.radius - distance) / mouse.radius;
|
||
this.x -= fx * force * 4;
|
||
this.y -= fy * force * 4;
|
||
}
|
||
};
|
||
|
||
function init() {
|
||
var sz = viewportSize();
|
||
var w = sz.w;
|
||
var h = sz.h;
|
||
cachedW = w;
|
||
cachedH = h;
|
||
|
||
var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
|
||
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);
|
||
|
||
particles = [];
|
||
var area = w * h;
|
||
var n = Math.floor(area / 14000);
|
||
if (n > 80) n = 80;
|
||
if (n < 28) n = 28;
|
||
if (w < 768) {
|
||
n = Math.min(n, 40);
|
||
}
|
||
for (var i = 0; i < n; i++) {
|
||
particles.push(new Particle(w, h));
|
||
}
|
||
}
|
||
|
||
function connect(w) {
|
||
var maxDist = w < 768 ? 110 : 140;
|
||
var a;
|
||
var b;
|
||
var dx;
|
||
var dy;
|
||
var distance;
|
||
var opacity;
|
||
var len = particles.length;
|
||
for (a = 0; a < len; a++) {
|
||
for (b = a + 1; b < len; b++) {
|
||
dx = particles[a].x - particles[b].x;
|
||
dy = particles[a].y - particles[b].y;
|
||
distance = Math.sqrt(dx * dx + dy * dy);
|
||
if (distance < maxDist) {
|
||
opacity = (1 - distance / maxDist) * 0.2;
|
||
ctx.strokeStyle = 'rgba(59, 130, 246,' + opacity + ')';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.moveTo(particles[a].x, particles[a].y);
|
||
ctx.lineTo(particles[b].x, particles[b].y);
|
||
ctx.stroke();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function frame() {
|
||
if (!running) return;
|
||
var w = cachedW;
|
||
var h = cachedH;
|
||
ctx.clearRect(0, 0, w, h);
|
||
|
||
var i;
|
||
for (i = 0; i < particles.length; i++) {
|
||
particles[i].update(w, h);
|
||
particles[i].draw();
|
||
}
|
||
connect(w);
|
||
raf = requestAnimationFrame(frame);
|
||
}
|
||
|
||
function start() {
|
||
if (reduced || document.hidden || running || !particles.length) return;
|
||
running = true;
|
||
raf = requestAnimationFrame(frame);
|
||
}
|
||
|
||
function stop() {
|
||
running = false;
|
||
if (raf) {
|
||
cancelAnimationFrame(raf);
|
||
raf = 0;
|
||
}
|
||
}
|
||
|
||
function onResize() {
|
||
stop();
|
||
init();
|
||
start();
|
||
}
|
||
|
||
function setMouseFromEvent(e) {
|
||
mouse.x = e.clientX;
|
||
mouse.y = e.clientY;
|
||
}
|
||
|
||
window.addEventListener(
|
||
'mousemove',
|
||
function (e) {
|
||
setMouseFromEvent(e);
|
||
},
|
||
{ passive: true },
|
||
);
|
||
|
||
window.addEventListener(
|
||
'scroll',
|
||
function () {
|
||
// Libera il main thread durante lo scroll; riprende subito dopo.
|
||
stop();
|
||
if (scrollPauseTimer) window.clearTimeout(scrollPauseTimer);
|
||
scrollPauseTimer = window.setTimeout(function () {
|
||
start();
|
||
}, 120);
|
||
},
|
||
{ passive: true },
|
||
);
|
||
|
||
document.addEventListener('visibilitychange', function () {
|
||
if (document.hidden) stop();
|
||
else start();
|
||
});
|
||
|
||
window.addEventListener('resize', onResize, { passive: true });
|
||
|
||
init();
|
||
start();
|
||
})();
|