Repository navigation
Expand file tree
/
Copy pathcursor-smoke.js
More file actions
123 lines (106 loc) · 2.6 KB
/
Copy pathcursor-smoke.js
File metadata and controls
123 lines (106 loc) · 2.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
if (window.innerWidth < 768) {
// reduce particles for mobile
PARTICLE_MULTIPLIER = 1;
}
const canvas = document.getElementById("smokeCursor");
const ctx = canvas.getContext("2d");
let w, h;
let particles = [];
let isTouch = "ontouchstart" in window;
function resize() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
}
resize();
window.addEventListener("resize", resize);
// Canvas setup
canvas.style.position = "fixed";
canvas.style.top = 0;
canvas.style.left = 0;
canvas.style.pointerEvents = "none";
canvas.style.zIndex = 9999;
// =====================
// DESKTOP SMOKE PARTICLE
// =====================
class SmokeParticle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 0.5 + 2;
this.life = 1;
this.vx = (Math.random() - 0.5) * 1.2;
this.vy = (Math.random() - 0.5) * 1.2;
this.hue = Math.random() * 360;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= 0.02;
this.size *= 0.97;
}
draw() {
ctx.beginPath();
ctx.fillStyle = `hsla(${this.hue}, 80%, 65%, ${this.life})`;
ctx.shadowColor = `hsla(${this.hue}, 80%, 65%, ${this.life})`;
ctx.shadowBlur = 20;
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
// =====================
// MOBILE GLOW PARTICLE
// =====================
class GlowParticle {
constructor(x, y) {
this.x = x;
this.y = y;
this.life = 1;
this.radius = 35;
}
update() {
this.life -= 0.05;
this.radius += 0.5;
}
draw() {
const gradient = ctx.createRadialGradient(
this.x, this.y, 0,
this.x, this.y, this.radius
);
gradient.addColorStop(0, `rgba(99,102,241,${this.life})`); // indigo
gradient.addColorStop(1, "rgba(99,102,241,0)");
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
}
}
// =====================
// EVENTS
// =====================
if (!isTouch) {
// Desktop mouse
window.addEventListener("mousemove", e => {
for (let i = 0; i < 3; i++) {
particles.push(new SmokeParticle(e.clientX, e.clientY));
}
});
} else {
// Mobile touch glow
window.addEventListener("touchmove", e => {
const t = e.touches[0];
particles.push(new GlowParticle(t.clientX, t.clientY));
}, { passive: true });
}
// =====================
// ANIMATION LOOP
// =====================
function animate() {
ctx.clearRect(0, 0, w, h);
particles = particles.filter(p => p.life > 0);
particles.forEach(p => {
p.update();
p.draw();
});
requestAnimationFrame(animate);
}
animate();