Click Cursor Effect

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

1
// @ts-nocheck
2
3
'use client';
4
5
import React, { useState, useEffect } from 'react';
6
import { motion, AnimatePresence } from 'framer-motion';
7
8
const ClickEffectCursor = () => {
9
const [mousePosition, setMousePosition] = useState({ x: null, y: null });
10
const [clicks, setClicks] = useState([]);
11
const [isHovering, setIsHovering] = useState(false);
12
const [trail, setTrail] = useState([]);
13
14
// Handle mouse movement
15
useEffect(() => {
16
const updateMousePosition = (e) => {
17
setMousePosition({ x: e.clientX, y: e.clientY });
18
19
// Add point to trail
20
setTrail((prev) => [
21
...prev.slice(-20),
22
{ x: e.clientX, y: e.clientY, id: Date.now() },
23
]);
24
};
25
26
window.addEventListener('mousemove', updateMousePosition);
27
return () => window.removeEventListener('mousemove', updateMousePosition);
28
}, []);
29
30
const handleClick = (e) => {
31
const newClick = {
32
x: e.clientX,
33
y: e.clientY,
34
id: Date.now(),
35
};
36
setClicks((prev) => [...prev, newClick]);
37
38
// Remove click effect after animation
39
setTimeout(() => {
40
setClicks((prev) => prev.filter((click) => click.id !== newClick.id));
41
}, 1000);
42
};
43
44
return (
45
<div
46
className='w-full bg-black'
47
onClick={handleClick}
48
onMouseEnter={() => setIsHovering(true)}
49
onMouseLeave={() => setIsHovering(false)}
50
>
51
{/* Mouse trail */}
52
<AnimatePresence>
53
{trail.map((point, index) => (
54
<motion.div
55
key={point.id}
56
className='fixed pointer-events-none'
57
initial={{ scale: 1, opacity: 0.5 }}
58
animate={{ scale: 0, opacity: 0 }}
59
exit={{ opacity: 0 }}
60
style={{
61
left: point.x,
62
top: point.y,
63
transform: 'translate(-50%, -50%)',
64
}}
65
transition={{ duration: 0.5 }}
66
>
67
<div
68
className='w-2 h-2 bg-pink-400 rounded-full'
69
style={{
70
opacity: (index / trail.length) * 0.5,
71
}}
72
/>
73
</motion.div>
74
))}
75
</AnimatePresence>
76
77
{/* Main cursor */}
78
{mousePosition.x !== null && mousePosition.y !== null && (
79
<motion.div
80
className='fixed pointer-events-none z-50'
81
animate={{
82
x: mousePosition.x,
83
y: mousePosition.y,
84
scale: isHovering ? 1.5 : 1,
85
}}
86
transition={{
87
type: 'spring',
88
stiffness: 500,
89
damping: 28,
90
mass: 0.5,
91
}}
92
style={{
93
transform: 'translate(-50%, -50%)',
94
}}
95
>
96
<div className='relative'>
97
<div className='w-6 h-6 bg-pink-500 rounded-full mix-blend-screen' />
98
<div className='absolute inset-0 w-6 h-6 border-2 border-pink-300 rounded-full animate-ping' />
99
</div>
100
</motion.div>
101
)}
102
103
{/* Click effects */}
104
<AnimatePresence>
105
{clicks.map((click) => (
106
<React.Fragment key={click.id}>
107
{/* Ripple effect */}
108
<motion.div
109
className='fixed pointer-events-none'
110
style={{
111
left: click.x,
112
top: click.y,
113
transform: 'translate(-50%, -50%)',
114
}}
115
initial={{ scale: 0, opacity: 1 }}
116
animate={{ scale: 2.5, opacity: 0 }}
117
exit={{ opacity: 0 }}
118
transition={{ duration: 0.8, ease: 'easeOut' }}
119
>
120
<div className='w-12 h-12 border-2 border-pink-400 rounded-full' />
121
</motion.div>
122
123
{/* Particle explosion */}
124
{[...Array(12)].map((_, i) => (
125
<motion.div
126
key={i}
127
className='fixed pointer-events-none w-2 h-2 bg-gradient-to-r from-pink-400 to-purple-500 rounded-full'
128
style={{
129
left: click.x,
130
top: click.y,
131
}}
132
initial={{ scale: 0 }}
133
animate={{
134
scale: [0, 1, 0],
135
x: Math.cos((i * Math.PI) / 6) * 80,
136
y: Math.sin((i * Math.PI) / 6) * 80,
137
opacity: [1, 0],
138
}}
139
transition={{
140
duration: 0.6,
141
ease: 'easeOut',
142
times: [0, 0.2, 1],
143
}}
144
/>
145
))}
146
</React.Fragment>
147
))}
148
</AnimatePresence>
149
</div>
150
);
151
};
152
153
export default ClickEffectCursor;
154

Installtion

1
npm install framer-motion