Neural Glow Cursor Effect
An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.
An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.
1import React from 'react'2import NeuralNoise from './neural-glow'34function index() {5return (6<div className='absolute w-full h-full overflow-hidden'>7<NeuralNoise/>8</div>9)10}1112export default index13
1// @ts-nocheck2'use client';3import React, { useEffect, useRef, useState } from 'react';45const NeuralGlow = () => {6const canvasRef = useRef(null);7const animationRef = useRef(null);8const glRef = useRef(null);9const uniformsRef = useRef(null);10const pointerRef = useRef({11x: 0,12y: 0,13tX: 0,14tY: 0,15});1617const vertexShaderSource = `18precision mediump float;1920varying vec2 vUv;21attribute vec2 a_position;2223void main() {24vUv = .5 * (a_position + 1.);25gl_Position = vec4(a_position, 0.0, 1.0);26}27`;2829const fragmentShaderSource = `30precision mediump float;3132varying vec2 vUv;33uniform float u_time;34uniform float u_ratio;35uniform vec2 u_pointer_position;36uniform float u_scroll_progress;3738vec2 rotate(vec2 uv, float th) {39return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;40}4142float neuro_shape(vec2 uv, float t, float p) {43vec2 sine_acc = vec2(0.);44vec2 res = vec2(0.);45float scale = 8.;4647for (int j = 0; j < 15; j++) {48uv = rotate(uv, 1.);49sine_acc = rotate(sine_acc, 1.);50vec2 layer = uv * scale + float(j) + sine_acc - t;51sine_acc += sin(layer) + 2.4 * p;52res += (.5 + .5 * cos(layer)) / scale;53scale *= (1.2);54}55return res.x + res.y;56}5758void main() {59vec2 uv = .5 * vUv;60uv.x *= u_ratio;6162vec2 pointer = vUv - u_pointer_position;63pointer.x *= u_ratio;64float p = clamp(length(pointer), 0., 1.);65p = .5 * pow(1. - p, 2.);6667float t = .001 * u_time;68vec3 color = vec3(0.);6970float noise = neuro_shape(uv, t, p);7172noise = 1.2 * pow(noise, 3.);73noise += pow(noise, 10.);74noise = max(.0, noise - .5);75noise *= (1. - length(vUv - .5));7677// Blue/indigo color palette78color = vec3(0.1, 0.2, 0.8); // Base blue color79color += vec3(0.0, 0.1, 0.4) * sin(3.0 * u_scroll_progress + 1.5); // Indigo variation8081color = color * noise;8283gl_FragColor = vec4(color, noise);84}85`;8687const createShader = (gl: any, sourceCode: any, type: any) => {88const shader = gl.createShader(type);89gl.shaderSource(shader, sourceCode);90gl.compileShader(shader);9192if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {93console.error(94'An error occurred compiling the shaders: ' +95gl.getShaderInfoLog(shader)96);97gl.deleteShader(shader);98return null;99}100101return shader;102};103104const createShaderProgram = (105gl: any,106vertexShader: any,107fragmentShader: any108) => {109const program = gl.createProgram();110gl.attachShader(program, vertexShader);111gl.attachShader(program, fragmentShader);112gl.linkProgram(program);113114if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {115console.error(116'Unable to initialize the shader program: ' +117gl.getProgramInfoLog(program)118);119return null;120}121122return program;123};124125const getUniforms = (gl: any, program: any) => {126let uniforms = [];127let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);128for (let i = 0; i < uniformCount; i++) {129let uniformName = gl.getActiveUniform(program, i).name;130uniforms[uniformName] = gl.getUniformLocation(program, uniformName);131}132return uniforms;133};134135const initShader = () => {136const canvas = canvasRef.current;137const gl =138canvas.getContext('webgl') || canvas.getContext('experimental-webgl');139140if (!gl) {141alert('WebGL is not supported by your browser.');142return null;143}144145const vertexShader = createShader(gl, vertexShaderSource, gl.VERTEX_SHADER);146const fragmentShader = createShader(147gl,148fragmentShaderSource,149gl.FRAGMENT_SHADER150);151152const shaderProgram = createShaderProgram(gl, vertexShader, fragmentShader);153uniformsRef.current = getUniforms(gl, shaderProgram);154155const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);156157const vertexBuffer = gl.createBuffer();158gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);159gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);160161gl.useProgram(shaderProgram);162163const positionLocation = gl.getAttribLocation(shaderProgram, 'a_position');164gl.enableVertexAttribArray(positionLocation);165166gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);167gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);168169return gl;170};171172const resizeCanvas = () => {173const canvas = canvasRef.current;174const gl = glRef.current;175if (!canvas || !gl) return;176177const devicePixelRatio = Math.min(window.devicePixelRatio, 2);178canvas.width = window.innerWidth * devicePixelRatio;179canvas.height = window.innerHeight * devicePixelRatio;180181gl.uniform1f(uniformsRef.current.u_ratio, canvas.width / canvas.height);182gl.viewport(0, 0, canvas.width, canvas.height);183};184185const render = () => {186const gl = glRef.current;187const uniforms = uniformsRef.current;188const pointer = pointerRef.current;189190if (!gl || !uniforms) return;191192const currentTime = performance.now();193194pointer.x += (pointer.tX - pointer.x) * 0.2;195pointer.y += (pointer.tY - pointer.y) * 0.2;196197gl.uniform1f(uniforms.u_time, currentTime);198gl.uniform2f(199uniforms.u_pointer_position,200pointer.x / window.innerWidth,2011 - pointer.y / window.innerHeight202);203gl.uniform1f(204uniforms.u_scroll_progress,205window.pageYOffset / (2 * window.innerHeight)206);207208gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);209animationRef.current = requestAnimationFrame(render);210};211212const updateMousePosition = (x, y) => {213pointerRef.current.tX = x;214pointerRef.current.tY = y;215};216217const handlePointerMove = (e) => {218updateMousePosition(e.clientX, e.clientY);219};220221const handleTouchMove = (e) => {222updateMousePosition(e.touches[0].clientX, e.touches[0].clientY);223};224225const handleClick = (e) => {226updateMousePosition(e.clientX, e.clientY);227};228229useEffect(() => {230glRef.current = initShader();231resizeCanvas();232render();233234const handleResize = () => {235resizeCanvas();236};237238window.addEventListener('resize', handleResize);239window.addEventListener('pointermove', handlePointerMove);240window.addEventListener('touchmove', handleTouchMove);241window.addEventListener('click', handleClick);242243return () => {244if (animationRef.current) {245cancelAnimationFrame(animationRef.current);246}247window.removeEventListener('resize', handleResize);248window.removeEventListener('pointermove', handlePointerMove);249window.removeEventListener('touchmove', handleTouchMove);250window.removeEventListener('click', handleClick);251};252}, []);253254return (255<>256<canvas257ref={canvasRef}258className='absolute top-0 left-0 w-full h-full pointer-events-none opacity-95'259style={{ backgroundColor: '#000000' }}260/>261</>262);263};264265export default NeuralGlow;