Neon 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.
1// @ts-nocheck2'use client';34import { useState, useEffect, useCallback } from 'react';5import { motion, useAnimation } from 'framer-motion';6import './NeonCursor.css';7// Attach your `neoncursor.css` file in your project.8// If you are using React, you can import the CSS directly into your `index.css` or another relevant CSS file.9// For Next.js, add the `neoncursor.css` styles to your global CSS file (e.g., `globals.css`).1011const NeonCursor = () => {12const [position, setPosition] = useState({13x: 0,14y: 0,15scale: 1,16opacity: 1,17});18const [isClicking, setIsClicking] = useState(false);19const [isHovering, setIsHovering] = useState(false);20const trailControls = useAnimation();21const glowControls = useAnimation();2223const handleMouseMove = useCallback((e) => {24setPosition((prev) => ({25...prev,26x: e.clientX,27y: e.clientY,28}));29}, []);3031const handleMouseDown = () => setIsClicking(true);32const handleMouseUp = () => setIsClicking(false);3334const handleMouseOver = useCallback(35(e) => {36const target = e.target;37if (target.matches('a, button, input, [data-hover="true"]')) {38setIsHovering(true);39void trailControls.start({40scale: 1.5,41borderColor: 'rgb(255, 150, 50)',42borderWidth: '3px',43});44void glowControls.start({45scale: 2,46opacity: 0.8,47});48}49},50[trailControls, glowControls]51);5253const handleMouseOut = useCallback(() => {54setIsHovering(false);55void trailControls.start({56scale: 1,57borderColor: 'rgb(236, 101, 23)',58borderWidth: '2px',59});60void glowControls.start({61scale: 1,62opacity: 0.4,63});64}, [trailControls, glowControls]);6566useEffect(() => {67window.addEventListener('mousemove', handleMouseMove);68window.addEventListener('mousedown', handleMouseDown);69window.addEventListener('mouseup', handleMouseUp);70window.addEventListener('mouseover', handleMouseOver);71window.addEventListener('mouseout', handleMouseOut);7273return () => {74window.removeEventListener('mousemove', handleMouseMove);75window.removeEventListener('mousedown', handleMouseDown);76window.removeEventListener('mouseup', handleMouseUp);77window.removeEventListener('mouseover', handleMouseOver);78window.removeEventListener('mouseout', handleMouseOut);79};80}, [handleMouseMove, handleMouseOver, handleMouseOut]);8182return (83<div className='neon-cursor-container'>84{/* Main cursor dot */}85<motion.div86className='cursor-main'87animate={{88x: position.x - 10,89y: position.y - 10,90scale: isClicking ? 0.8 : isHovering ? 1.2 : 1,91}}92transition={{93type: 'spring',94damping: 20,95stiffness: 400,96mass: 0.5,97}}98/>99100{/* Trailing circle */}101<motion.div102className='cursor-trail'103animate={{104x: position.x - 20,105y: position.y - 20,106}}107transition={{108type: 'spring',109damping: 30,110stiffness: 200,111mass: 0.8,112}}113initial={false}114/>115116{/* Outer glow */}117<motion.div118className='cursor-glow'119animate={{120x: position.x - 30,121y: position.y - 30,122}}123transition={{124type: 'spring',125damping: 40,126stiffness: 150,127mass: 1,128}}129initial={false}130/>131</div>132);133};134135export default NeonCursor;136
1// @ts-nocheck23import { useEffect } from 'react';45const useCanvasCursor = () => {6function n(e) {7this.init(e || {});8}9n.prototype = {10init: function (e) {11this.phase = e.phase || 0;12this.offset = e.offset || 0;13this.frequency = e.frequency || 0.001;14this.amplitude = e.amplitude || 1;15},16update: function () {17return (18(this.phase += this.frequency),19(e = this.offset + Math.sin(this.phase) * this.amplitude)20);21},22value: function () {23return e;24},25};2627function Line(e) {28this.init(e || {});29}3031Line.prototype = {32init: function (e) {33this.spring = e.spring + 0.1 * Math.random() - 0.02;34this.friction = E.friction + 0.01 * Math.random() - 0.002;35this.nodes = [];36for (var t, n = 0; n < E.size; n++) {37t = new Node();38t.x = pos.x;39t.y = pos.y;40this.nodes.push(t);41}42},43update: function () {44var e = this.spring,45t = this.nodes[0];46t.vx += (pos.x - t.x) * e;47t.vy += (pos.y - t.y) * e;48for (var n, i = 0, a = this.nodes.length; i < a; i++)49(t = this.nodes[i]),500 < 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},62draw: function () {63var e,64t,65n = this.nodes[0].x,66i = this.nodes[0].y;67ctx.beginPath();68ctx.moveTo(n, i);69for (var a = 1, o = this.nodes.length - 2; a < o; a++) {70e = this.nodes[a];71t = this.nodes[a + 1];72n = 0.5 * (e.x + t.x);73i = 0.5 * (e.y + t.y);74ctx.quadraticCurveTo(e.x, e.y, n, i);75}76e = this.nodes[a];77t = this.nodes[a + 1];78ctx.quadraticCurveTo(e.x, e.y, t.x, t.y);79ctx.stroke();80ctx.closePath();81},82};8384function onMousemove(e) {85function o() {86lines = [];87for (var e = 0; e < E.trails; e++)88lines.push(new Line({ spring: 0.4 + (e / E.trails) * 0.025 }));89}90function c(e) {91e.touches92? ((pos.x = e.touches[0].pageX), (pos.y = e.touches[0].pageY))93: ((pos.x = e.clientX), (pos.y = e.clientY)),94e.preventDefault();95}96function l(e) {971 == e.touches.length &&98((pos.x = e.touches[0].pageX), (pos.y = e.touches[0].pageY));99}100document.removeEventListener('mousemove', onMousemove),101document.removeEventListener('touchstart', onMousemove),102document.addEventListener('mousemove', c),103document.addEventListener('touchmove', c),104document.addEventListener('touchstart', l),105c(e),106o(),107render();108}109110function render() {111if (ctx.running) {112ctx.globalCompositeOperation = 'source-over';113ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);114ctx.globalCompositeOperation = 'lighter';115ctx.strokeStyle = 'hsla(' + Math.round(f.update()) + ',50%,50%,0.2)';116ctx.lineWidth = 1;117for (var e, t = 0; t < E.trails; t++) {118(e = lines[t]).update();119e.draw();120}121ctx.frame++;122window.requestAnimationFrame(render);123}124}125126function resizeCanvas() {127ctx.canvas.width = window.innerWidth - 20;128ctx.canvas.height = window.innerHeight;129}130131var ctx,132f,133e = 0,134pos = {},135lines = [],136E = {137debug: true,138friction: 0.5,139trails: 20,140size: 50,141dampening: 0.25,142tension: 0.98,143};144function Node() {145this.x = 0;146this.y = 0;147this.vy = 0;148this.vx = 0;149}150151const renderCanvas = function () {152ctx = document.getElementById('canvas').getContext('2d');153ctx.running = true;154ctx.frame = 1;155f = new n({156phase: Math.random() * 2 * Math.PI,157amplitude: 85,158frequency: 0.0015,159offset: 285,160});161document.addEventListener('mousemove', onMousemove);162document.addEventListener('touchstart', onMousemove);163document.body.addEventListener('orientationchange', resizeCanvas);164window.addEventListener('resize', resizeCanvas);165window.addEventListener('focus', () => {166if (!ctx.running) {167ctx.running = true;168render();169}170});171window.addEventListener('blur', () => {172ctx.running = true;173});174resizeCanvas();175};176177useEffect(() => {178renderCanvas();179180return () => {181ctx.running = false;182document.removeEventListener('mousemove', onMousemove);183document.removeEventListener('touchstart', onMousemove);184document.body.removeEventListener('orientationchange', resizeCanvas);185window.removeEventListener('resize', resizeCanvas);186window.removeEventListener('focus', () => {187if (!ctx.running) {188ctx.running = true;189render();190}191});192window.removeEventListener('blur', () => {193ctx.running = true;194});195};196}, []);197};198199export default useCanvasCursor;
Prop | Type | Default | Description |
---|---|---|---|
classname | string | Optional CSS class for styling the main vignette container. | |
children | React.ReactNode | The content to display inside the vignette effect. | |
radius | string | 24px | The radius for the vignette effect. |
inset | string | 20px | The inset value for the vignette effect. |
transitionLength | string | 44px | The length of the transition effect applied to the vignette. |
blur | string | 6px | The blur amount for the vignette effect. |
blurclassname | string | Optional CSS class for styling the blur effect container. |