Neural Glow Cursor Effect

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

1
import React from 'react'
2
import NeuralNoise from './neural-glow'
3
4
function index() {
5
return (
6
<div className='absolute w-full h-full overflow-hidden'>
7
<NeuralNoise/>
8
</div>
9
)
10
}
11
12
export default index
13
neural-glow.tsx
1
// @ts-nocheck
2
'use client';
3
import React, { useEffect, useRef, useState } from 'react';
4
5
const NeuralGlow = () => {
6
const canvasRef = useRef(null);
7
const animationRef = useRef(null);
8
const glRef = useRef(null);
9
const uniformsRef = useRef(null);
10
const pointerRef = useRef({
11
x: 0,
12
y: 0,
13
tX: 0,
14
tY: 0,
15
});
16
17
const vertexShaderSource = `
18
precision mediump float;
19
20
varying vec2 vUv;
21
attribute vec2 a_position;
22
23
void main() {
24
vUv = .5 * (a_position + 1.);
25
gl_Position = vec4(a_position, 0.0, 1.0);
26
}
27
`;
28
29
const fragmentShaderSource = `
30
precision mediump float;
31
32
varying vec2 vUv;
33
uniform float u_time;
34
uniform float u_ratio;
35
uniform vec2 u_pointer_position;
36
uniform float u_scroll_progress;
37
38
vec2 rotate(vec2 uv, float th) {
39
return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;
40
}
41
42
float neuro_shape(vec2 uv, float t, float p) {
43
vec2 sine_acc = vec2(0.);
44
vec2 res = vec2(0.);
45
float scale = 8.;
46
47
for (int j = 0; j < 15; j++) {
48
uv = rotate(uv, 1.);
49
sine_acc = rotate(sine_acc, 1.);
50
vec2 layer = uv * scale + float(j) + sine_acc - t;
51
sine_acc += sin(layer) + 2.4 * p;
52
res += (.5 + .5 * cos(layer)) / scale;
53
scale *= (1.2);
54
}
55
return res.x + res.y;
56
}
57
58
void main() {
59
vec2 uv = .5 * vUv;
60
uv.x *= u_ratio;
61
62
vec2 pointer = vUv - u_pointer_position;
63
pointer.x *= u_ratio;
64
float p = clamp(length(pointer), 0., 1.);
65
p = .5 * pow(1. - p, 2.);
66
67
float t = .001 * u_time;
68
vec3 color = vec3(0.);
69
70
float noise = neuro_shape(uv, t, p);
71
72
noise = 1.2 * pow(noise, 3.);
73
noise += pow(noise, 10.);
74
noise = max(.0, noise - .5);
75
noise *= (1. - length(vUv - .5));
76
77
// Blue/indigo color palette
78
color = vec3(0.1, 0.2, 0.8); // Base blue color
79
color += vec3(0.0, 0.1, 0.4) * sin(3.0 * u_scroll_progress + 1.5); // Indigo variation
80
81
color = color * noise;
82
83
gl_FragColor = vec4(color, noise);
84
}
85
`;
86
87
const createShader = (gl: any, sourceCode: any, type: any) => {
88
const shader = gl.createShader(type);
89
gl.shaderSource(shader, sourceCode);
90
gl.compileShader(shader);
91
92
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
93
console.error(
94
'An error occurred compiling the shaders: ' +
95
gl.getShaderInfoLog(shader)
96
);
97
gl.deleteShader(shader);
98
return null;
99
}
100
101
return shader;
102
};
103
104
const createShaderProgram = (
105
gl: any,
106
vertexShader: any,
107
fragmentShader: any
108
) => {
109
const program = gl.createProgram();
110
gl.attachShader(program, vertexShader);
111
gl.attachShader(program, fragmentShader);
112
gl.linkProgram(program);
113
114
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
115
console.error(
116
'Unable to initialize the shader program: ' +
117
gl.getProgramInfoLog(program)
118
);
119
return null;
120
}
121
122
return program;
123
};
124
125
const getUniforms = (gl: any, program: any) => {
126
let uniforms = [];
127
let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
128
for (let i = 0; i < uniformCount; i++) {
129
let uniformName = gl.getActiveUniform(program, i).name;
130
uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
131
}
132
return uniforms;
133
};
134
135
const initShader = () => {
136
const canvas = canvasRef.current;
137
const gl =
138
canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
139
140
if (!gl) {
141
alert('WebGL is not supported by your browser.');
142
return null;
143
}
144
145
const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
146
const fragmentShader = createShader(
147
gl,
148
fragmentShaderSource,
149
gl.FRAGMENT_SHADER
150
);
151
152
const shaderProgram = createShaderProgram(gl, vertexShader, fragmentShader);
153
uniformsRef.current = getUniforms(gl, shaderProgram);
154
155
const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
156
157
const vertexBuffer = gl.createBuffer();
158
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
159
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
160
161
gl.useProgram(shaderProgram);
162
163
const positionLocation = gl.getAttribLocation(shaderProgram, 'a_position');
164
gl.enableVertexAttribArray(positionLocation);
165
166
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
167
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
168
169
return gl;
170
};
171
172
const resizeCanvas = () => {
173
const canvas = canvasRef.current;
174
const gl = glRef.current;
175
if (!canvas || !gl) return;
176
177
const devicePixelRatio = Math.min(window.devicePixelRatio, 2);
178
canvas.width = window.innerWidth * devicePixelRatio;
179
canvas.height = window.innerHeight * devicePixelRatio;
180
181
gl.uniform1f(uniformsRef.current.u_ratio, canvas.width / canvas.height);
182
gl.viewport(0, 0, canvas.width, canvas.height);
183
};
184
185
const render = () => {
186
const gl = glRef.current;
187
const uniforms = uniformsRef.current;
188
const pointer = pointerRef.current;
189
190
if (!gl || !uniforms) return;
191
192
const currentTime = performance.now();
193
194
pointer.x += (pointer.tX - pointer.x) * 0.2;
195
pointer.y += (pointer.tY - pointer.y) * 0.2;
196
197
gl.uniform1f(uniforms.u_time, currentTime);
198
gl.uniform2f(
199
uniforms.u_pointer_position,
200
pointer.x / window.innerWidth,
201
1 - pointer.y / window.innerHeight
202
);
203
gl.uniform1f(
204
uniforms.u_scroll_progress,
205
window.pageYOffset / (2 * window.innerHeight)
206
);
207
208
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
209
animationRef.current = requestAnimationFrame(render);
210
};
211
212
const updateMousePosition = (x, y) => {
213
pointerRef.current.tX = x;
214
pointerRef.current.tY = y;
215
};
216
217
const handlePointerMove = (e) => {
218
updateMousePosition(e.clientX, e.clientY);
219
};
220
221
const handleTouchMove = (e) => {
222
updateMousePosition(e.touches[0].clientX, e.touches[0].clientY);
223
};
224
225
const handleClick = (e) => {
226
updateMousePosition(e.clientX, e.clientY);
227
};
228
229
useEffect(() => {
230
glRef.current = initShader();
231
resizeCanvas();
232
render();
233
234
const handleResize = () => {
235
resizeCanvas();
236
};
237
238
window.addEventListener('resize', handleResize);
239
window.addEventListener('pointermove', handlePointerMove);
240
window.addEventListener('touchmove', handleTouchMove);
241
window.addEventListener('click', handleClick);
242
243
return () => {
244
if (animationRef.current) {
245
cancelAnimationFrame(animationRef.current);
246
}
247
window.removeEventListener('resize', handleResize);
248
window.removeEventListener('pointermove', handlePointerMove);
249
window.removeEventListener('touchmove', handleTouchMove);
250
window.removeEventListener('click', handleClick);
251
};
252
}, []);
253
254
return (
255
<>
256
<canvas
257
ref={canvasRef}
258
className='absolute top-0 left-0 w-full h-full pointer-events-none opacity-95'
259
style={{ backgroundColor: '#000000' }}
260
/>
261
</>
262
);
263
};
264
265
export default NeuralGlow;