Riduce i micro-blocchi allo scroll alleggerendo canvas e spine SVG.
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>
This commit is contained in:
@ -1,27 +1,28 @@
|
|||||||
/**
|
/**
|
||||||
* Rete di particelle + linee (canvas #particleCanvas).
|
* Rete di particelle + linee (canvas #particleCanvas).
|
||||||
* Deve stare DENTRO il contenitore della pagina (es. .nx-grid-bg), non dietro body,
|
* Limitato al viewport (non all’altezza documento): evita canvas giganti che
|
||||||
* altrimenti lo sfondo opaco della pagina lo copre.
|
* bloccano lo scroll sulla home lunga.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
var canvas = document.getElementById('particleCanvas');
|
var canvas = document.getElementById('particleCanvas');
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
var ctx = canvas.getContext('2d');
|
var ctx = canvas.getContext('2d', { alpha: true });
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
var particles = [];
|
var particles = [];
|
||||||
var mouse = { x: null, y: null, radius: 150 };
|
var mouse = { x: null, y: null, radius: 150 };
|
||||||
var raf = 0;
|
var raf = 0;
|
||||||
|
var running = false;
|
||||||
|
var cachedW = 0;
|
||||||
|
var cachedH = 0;
|
||||||
|
var scrollPauseTimer = 0;
|
||||||
|
|
||||||
function canvasSize() {
|
function viewportSize() {
|
||||||
var p = canvas.parentElement;
|
var w = Math.max(1, Math.floor(window.innerWidth || document.documentElement.clientWidth || 1));
|
||||||
if (!p) return { w: 0, h: 0 };
|
var h = Math.max(1, Math.floor(window.innerHeight || document.documentElement.clientHeight || 1));
|
||||||
var w = Math.max(1, Math.floor(p.clientWidth));
|
|
||||||
var h = Math.max(1, Math.floor(p.clientHeight));
|
|
||||||
return { w: w, h: h };
|
return { w: w, h: h };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,8 +30,8 @@
|
|||||||
this.x = Math.random() * w;
|
this.x = Math.random() * w;
|
||||||
this.y = Math.random() * h;
|
this.y = Math.random() * h;
|
||||||
this.size = Math.random() * 2 + 1;
|
this.size = Math.random() * 2 + 1;
|
||||||
this.speedX = (Math.random() - 0.5) * 1.5;
|
this.speedX = (Math.random() - 0.5) * 1.2;
|
||||||
this.speedY = (Math.random() - 0.5) * 1.5;
|
this.speedY = (Math.random() - 0.5) * 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
Particle.prototype.draw = function () {
|
Particle.prototype.draw = function () {
|
||||||
@ -58,46 +59,55 @@
|
|||||||
var fx = dx / distance;
|
var fx = dx / distance;
|
||||||
var fy = dy / distance;
|
var fy = dy / distance;
|
||||||
var force = (mouse.radius - distance) / mouse.radius;
|
var force = (mouse.radius - distance) / mouse.radius;
|
||||||
this.x -= fx * force * 5;
|
this.x -= fx * force * 4;
|
||||||
this.y -= fy * force * 5;
|
this.y -= fy * force * 4;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var sz = canvasSize();
|
var sz = viewportSize();
|
||||||
var w = sz.w;
|
var w = sz.w;
|
||||||
var h = sz.h;
|
var h = sz.h;
|
||||||
if (w < 8 || h < 8) return;
|
cachedW = w;
|
||||||
|
cachedH = h;
|
||||||
|
|
||||||
var dpr = Math.min(window.devicePixelRatio || 1, 2);
|
var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
|
||||||
canvas.width = Math.floor(w * dpr);
|
canvas.width = Math.floor(w * dpr);
|
||||||
canvas.height = Math.floor(h * 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);
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
|
||||||
particles = [];
|
particles = [];
|
||||||
var n = Math.floor((w * h) / 9000);
|
var area = w * h;
|
||||||
if (n > 130) n = 130;
|
var n = Math.floor(area / 14000);
|
||||||
if (n < 45) n = 45;
|
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++) {
|
for (var i = 0; i < n; i++) {
|
||||||
particles.push(new Particle(w, h));
|
particles.push(new Particle(w, h));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect(w, h) {
|
function connect(w) {
|
||||||
|
var maxDist = w < 768 ? 110 : 140;
|
||||||
var a;
|
var a;
|
||||||
var b;
|
var b;
|
||||||
var dx;
|
var dx;
|
||||||
var dy;
|
var dy;
|
||||||
var distance;
|
var distance;
|
||||||
var opacity;
|
var opacity;
|
||||||
for (a = 0; a < particles.length; a++) {
|
var len = particles.length;
|
||||||
for (b = a + 1; b < particles.length; b++) {
|
for (a = 0; a < len; a++) {
|
||||||
|
for (b = a + 1; b < len; b++) {
|
||||||
dx = particles[a].x - particles[b].x;
|
dx = particles[a].x - particles[b].x;
|
||||||
dy = particles[a].y - particles[b].y;
|
dy = particles[a].y - particles[b].y;
|
||||||
distance = Math.sqrt(dx * dx + dy * dy);
|
distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
if (distance < 150) {
|
if (distance < maxDist) {
|
||||||
opacity = (1 - distance / 150) * 0.22;
|
opacity = (1 - distance / maxDist) * 0.2;
|
||||||
ctx.strokeStyle = 'rgba(59, 130, 246, ' + opacity + ')';
|
ctx.strokeStyle = 'rgba(59, 130, 246,' + opacity + ')';
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(particles[a].x, particles[a].y);
|
ctx.moveTo(particles[a].x, particles[a].y);
|
||||||
@ -109,9 +119,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function frame() {
|
function frame() {
|
||||||
var sz = canvasSize();
|
if (!running) return;
|
||||||
var w = sz.w;
|
var w = cachedW;
|
||||||
var h = sz.h;
|
var h = cachedH;
|
||||||
ctx.clearRect(0, 0, w, h);
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
var i;
|
var i;
|
||||||
@ -119,29 +129,33 @@
|
|||||||
particles[i].update(w, h);
|
particles[i].update(w, h);
|
||||||
particles[i].draw();
|
particles[i].draw();
|
||||||
}
|
}
|
||||||
connect(w, h);
|
connect(w);
|
||||||
raf = requestAnimationFrame(frame);
|
raf = requestAnimationFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncCanvasCssSize() {
|
function start() {
|
||||||
var sz = canvasSize();
|
if (reduced || document.hidden || running || !particles.length) return;
|
||||||
canvas.style.width = sz.w + 'px';
|
running = true;
|
||||||
canvas.style.height = sz.h + 'px';
|
raf = requestAnimationFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onResize() {
|
function stop() {
|
||||||
cancelAnimationFrame(raf);
|
running = false;
|
||||||
syncCanvasCssSize();
|
if (raf) {
|
||||||
init();
|
cancelAnimationFrame(raf);
|
||||||
if (!reduced && particles.length) {
|
raf = 0;
|
||||||
raf = requestAnimationFrame(frame);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onResize() {
|
||||||
|
stop();
|
||||||
|
init();
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
function setMouseFromEvent(e) {
|
function setMouseFromEvent(e) {
|
||||||
var rect = canvas.getBoundingClientRect();
|
mouse.x = e.clientX;
|
||||||
mouse.x = e.clientX - rect.left;
|
mouse.y = e.clientY;
|
||||||
mouse.y = e.clientY - rect.top;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
@ -152,22 +166,26 @@
|
|||||||
{ passive: true },
|
{ passive: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
var parent = canvas.parentElement;
|
window.addEventListener(
|
||||||
if (parent) {
|
'scroll',
|
||||||
var ro = new ResizeObserver(onResize);
|
function () {
|
||||||
ro.observe(parent);
|
// 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 });
|
window.addEventListener('resize', onResize, { passive: true });
|
||||||
|
|
||||||
syncCanvasCssSize();
|
|
||||||
init();
|
init();
|
||||||
|
start();
|
||||||
if (reduced) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (particles.length) {
|
|
||||||
raf = requestAnimationFrame(frame);
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -3,32 +3,20 @@
|
|||||||
* Continuazione dei flussi dati dell'hero: dalla destra verso sinistra,
|
* Continuazione dei flussi dati dell'hero: dalla destra verso sinistra,
|
||||||
* inclinazione verso il basso, fino alla fascia del titolo "Prodotti".
|
* inclinazione verso il basso, fino alla fascia del titolo "Prodotti".
|
||||||
* Dietro a testo e card (main ha stacking; questo blocco è z-0).
|
* Dietro a testo e card (main ha stacking; questo blocco è z-0).
|
||||||
|
* Senza feGaussianBlur: il filtro SVG su un layer alto quanto il main
|
||||||
|
* rendeva lo scroll a scatti.
|
||||||
*/
|
*/
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="nx-dataflow-spine" aria-hidden="true">
|
<div class="nx-dataflow-spine" aria-hidden="true">
|
||||||
<svg
|
<svg
|
||||||
class="nx-dataflow-spine-svg"
|
class="nx-dataflow-spine-svg"
|
||||||
viewBox="0 0 1480 2800"
|
viewBox="0 0 1480 1600"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<filter
|
|
||||||
id="nx-spine-glow"
|
|
||||||
x="-30%"
|
|
||||||
y="-30%"
|
|
||||||
width="160%"
|
|
||||||
height="160%"
|
|
||||||
color-interpolation-filters="sRGB"
|
|
||||||
>
|
|
||||||
<feGaussianBlur stdDeviation="2.2" result="blur" />
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode in="blur" />
|
|
||||||
<feMergeNode in="SourceGraphic" />
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
<linearGradient id="nx-spine-stroke" x1="100%" y1="0%" x2="0%" y2="60%">
|
<linearGradient id="nx-spine-stroke" x1="100%" y1="0%" x2="0%" y2="60%">
|
||||||
<stop offset="0%" stop-color="#a5f3fc" stop-opacity="0.75" />
|
<stop offset="0%" stop-color="#a5f3fc" stop-opacity="0.75" />
|
||||||
<stop offset="40%" stop-color="#38bdf8" stop-opacity="0.9" />
|
<stop offset="40%" stop-color="#38bdf8" stop-opacity="0.9" />
|
||||||
@ -36,8 +24,8 @@
|
|||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<g filter="url(#nx-spine-glow)" opacity="0.72">
|
<g opacity="0.72">
|
||||||
<!-- Entrata da dx (fuori canvas) → curva verso sx con pendenza verso il basso; fascia ~y 980–1280 = titolo Prodotti su viewport tipiche -->
|
<!-- Entrata da dx → curva verso sx; fascia titolo Prodotti -->
|
||||||
<path
|
<path
|
||||||
class="nx-flow-line nx-flow-line--a"
|
class="nx-flow-line nx-flow-line--a"
|
||||||
vector-effect="non-scaling-stroke"
|
vector-effect="non-scaling-stroke"
|
||||||
|
|||||||
@ -36,14 +36,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Canvas particelle: dentro .nx-grid-bg, sopra lo sfondo della sezione */
|
/* Canvas particelle: fisso al viewport (non alto quanto tutto il documento) */
|
||||||
.nx-particle-layer {
|
.nx-particle-layer {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subtle grid (Vercel-like) + vignette */
|
/* Subtle grid (Vercel-like) + vignette */
|
||||||
@ -128,11 +129,16 @@
|
|||||||
/* Spine: prolunga i flussi hero verso la fascia “Prodotti” (dietro al contenuto) */
|
/* Spine: prolunga i flussi hero verso la fascia “Prodotti” (dietro al contenuto) */
|
||||||
.nx-dataflow-spine {
|
.nx-dataflow-spine {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
/* Solo fascia hero → prodotti: evita SVG filtrato alto quanto tutto il main */
|
||||||
|
height: min(160vh, 2200px);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
opacity: 0.46;
|
opacity: 0.46;
|
||||||
|
contain: paint;
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
to bottom,
|
to bottom,
|
||||||
transparent 0,
|
transparent 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user