Canvas Cursor Effect

An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.

1
'use client';
2
3
import useCanvasCursor from '@/hooks/use-canvasCursor';
4
5
const CanvasCursor = () => {
6
useCanvasCursor();
7
8
return <canvas className='pointer-events-none fixed inset-0' id='canvas' />;
9
};
10
export default CanvasCursor;
11

useCanvasCursor

1
// @ts-nocheck
2
3
import { useEffect } from 'react';
4
5
const useCanvasCursor = () => {
6
function n(e) {
7
this.init(e || {});
8
}
9
n.prototype = {
10
init: function (e) {
11
this.phase = e.phase || 0;
12
this.offset = e.offset || 0;
13
this.frequency = e.frequency || 0.001;
14
this.amplitude = e.amplitude || 1;
15
},
16
update: function () {
17
return (
18
(this.phase += this.frequency),
19
(e = this.offset + Math.sin(this.phase) * this.amplitude)
20
);
21
},
22
value: function () {
23
return e;
24
},
25
};
26
27
function Line(e) {
28
this.init(e || {});
29
}
30
31
Line.prototype = {
32
init: function (e) {
33
this.spring = e.spring + 0.1 * Math.random() - 0.02;
34
this.friction = E.friction + 0.01 * Math.random() - 0.002;
35
this.nodes = [];
36
for (var t, n = 0; n < E.size; n++) {
37
t = new Node();
38
t.x = pos.x;
39
t.y = pos.y;
40
this.nodes.push(t);
41
}
42
},
43
update: function () {
44
var e = this.spring,
45
t = this.nodes[0];
46
t.vx += (pos.x - t.x) * e;
47
t.vy += (pos.y - t.y) * e;
48
for (var n, i = 0, a = this.nodes.length; i < a; i++)
49
(t = this.nodes[i]),
50
0 < i &&
51
((n = this.nodes[i - 1]),
52
(t.vx += (n.x - t.x) * e),
53
(t.vy += (n.y - t.y) * e),
54
(t.vx += n.vx * E.dampening),
55
(t.vy += n.vy * E.dampening)),
56
(t.vx *= this.friction),
57
(t.vy *= this.friction),
58
(t.x += t.vx),
59
(t.y += t.vy),
60
(e *= E.tension);
61
},
62
draw: function () {
63
var e,
64
t,
65
n = this.nodes[0].x,
66
i = this.nodes[0].y;
67
ctx.beginPath();
68
ctx.moveTo(n, i);
69
for (var a = 1, o = this.nodes.length - 2; a < o; a++) {
70
e = this.nodes[a];
71
t = this.nodes[a + 1];
72
n = 0.5 * (e.x + t.x);
73
i = 0.5 * (e.y + t.y);
74
ctx.quadraticCurveTo(e.x, e.y, n, i);
75
}
76
e = this.nodes[a];
77
t = this.nodes[a + 1];
78
ctx.quadraticCurveTo(e.x, e.y, t.x, t.y);
79
ctx.stroke();
80
ctx.closePath();
81
},
82
};
83
84
function onMousemove(e) {
85
function o() {
86
lines = [];
87
for (var e = 0; e < E.trails; e++)
88
lines.push(new Line({ spring: 0.4 + (e / E.trails) * 0.025 }));
89
}
90
function c(e) {
91
e.touches
92
? ((pos.x = e.touches[0].pageX), (pos.y = e.touches[0].pageY))
93
: ((pos.x = e.clientX), (pos.y = e.clientY)),
94
e.preventDefault();
95
}
96
function l(e) {
97
1 == e.touches.length &&
98
((pos.x = e.touches[0].pageX), (pos.y = e.touches[0].pageY));
99
}
100
document.removeEventListener('mousemove', onMousemove),
101
document.removeEventListener('touchstart', onMousemove),
102
document.addEventListener('mousemove', c),
103
document.addEventListener('touchmove', c),
104
document.addEventListener('touchstart', l),
105
c(e),
106
o(),
107
render();
108
}
109
110
function render() {
111
if (ctx.running) {
112
ctx.globalCompositeOperation = 'source-over';
113
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
114
ctx.globalCompositeOperation = 'lighter';
115
ctx.strokeStyle = 'hsla(' + Math.round(f.update()) + ',50%,50%,0.2)';
116
ctx.lineWidth = 1;
117
for (var e, t = 0; t < E.trails; t++) {
118
(e = lines[t]).update();
119
e.draw();
120
}
121
ctx.frame++;
122
window.requestAnimationFrame(render);
123
}
124
}
125
126
function resizeCanvas() {
127
ctx.canvas.width = window.innerWidth - 20;
128
ctx.canvas.height = window.innerHeight;
129
}
130
131
var ctx,
132
f,
133
e = 0,
134
pos = {},
135
lines = [],
136
E = {
137
debug: true,
138
friction: 0.5,
139
trails: 20,
140
size: 50,
141
dampening: 0.25,
142
tension: 0.98,
143
};
144
function Node() {
145
this.x = 0;
146
this.y = 0;
147
this.vy = 0;
148
this.vx = 0;
149
}
150
151
const renderCanvas = function () {
152
ctx = document.getElementById('canvas').getContext('2d');
153
ctx.running = true;
154
ctx.frame = 1;
155
f = new n({
156
phase: Math.random() * 2 * Math.PI,
157
amplitude: 85,
158
frequency: 0.0015,
159
offset: 285,
160
});
161
document.addEventListener('mousemove', onMousemove);
162
document.addEventListener('touchstart', onMousemove);
163
document.body.addEventListener('orientationchange', resizeCanvas);
164
window.addEventListener('resize', resizeCanvas);
165
window.addEventListener('focus', () => {
166
if (!ctx.running) {
167
ctx.running = true;
168
render();
169
}
170
});
171
window.addEventListener('blur', () => {
172
ctx.running = true;
173
});
174
resizeCanvas();
175
};
176
177
useEffect(() => {
178
renderCanvas();
179
180
return () => {
181
ctx.running = false;
182
document.removeEventListener('mousemove', onMousemove);
183
document.removeEventListener('touchstart', onMousemove);
184
document.body.removeEventListener('orientationchange', resizeCanvas);
185
window.removeEventListener('resize', resizeCanvas);
186
window.removeEventListener('focus', () => {
187
if (!ctx.running) {
188
ctx.running = true;
189
render();
190
}
191
});
192
window.removeEventListener('blur', () => {
193
ctx.running = true;
194
});
195
};
196
}, []);
197
};
198
199
export default useCanvasCursor;