3d Cursor Effect

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

1
// @ts-nocheck
2
'use client';
3
4
import { useState, useEffect } from 'react';
5
import { useMouse } from '@/hooks/use-mouse';
6
7
const MultiCursor = () => {
8
const [mouseState, ref] = useMouse();
9
const [cursors, setCursors] = useState([]);
10
const [pattern, setPattern] = useState('circle');
11
const [rotation, setRotation] = useState(0);
12
13
useEffect(() => {
14
const interval = setInterval(() => {
15
setRotation((prev) => (prev + 2) % 360);
16
}, 16);
17
return () => clearInterval(interval);
18
}, []);
19
20
useEffect(() => {
21
if (mouseState.x && mouseState.y) {
22
const patterns = {
23
circle: Array(8)
24
.fill(0)
25
.map((_, i) => ({
26
x:
27
Math.cos((i * Math.PI * 2) / 8 + (rotation * Math.PI) / 180) * 50,
28
y:
29
Math.sin((i * Math.PI * 2) / 8 + (rotation * Math.PI) / 180) * 50,
30
scale: 0.5 + Math.sin((rotation * Math.PI) / 180 + i) * 0.2,
31
})),
32
spiral: Array(12)
33
.fill(0)
34
.map((_, i) => {
35
const angle = (i * Math.PI * 2) / 12 + (rotation * Math.PI) / 180;
36
const radius = i * 5;
37
return {
38
x: Math.cos(angle) * radius,
39
y: Math.sin(angle) * radius,
40
scale: 1 - i * 0.05,
41
};
42
}),
43
grid: Array(9)
44
.fill(0)
45
.map((_, i) => ({
46
x: ((i % 3) - 1) * 40 * Math.cos((rotation * Math.PI) / 180),
47
y:
48
(Math.floor(i / 3) - 1) *
49
40 *
50
Math.sin((rotation * Math.PI) / 180),
51
scale: 0.5 + Math.sin((rotation * Math.PI) / 180 + i) * 0.2,
52
})),
53
};
54
55
setCursors(patterns[pattern]);
56
}
57
}, [mouseState, pattern, rotation]);
58
59
return (
60
<div className='relative w-full h-full' ref={ref}>
61
{mouseState.x !== null && mouseState.y !== null && (
62
<>
63
{/* Multi-cursors */}
64
{cursors.map((cursor, index) => (
65
<div
66
key={index}
67
className='fixed pointer-events-none transition-all duration-200'
68
style={{
69
left: mouseState.x + cursor.x,
70
top: mouseState.y + cursor.y,
71
transform: `translate(-50%, -50%) scale(${cursor.scale})`,
72
}}
73
>
74
<div
75
className='w-6 h-6 rounded-full mix-blend-screen'
76
style={{
77
background: `hsl(${(index * 30 + rotation) % 360}, 100%, 70%)`,
78
}}
79
/>
80
</div>
81
))}
82
83
{/* Main cursor */}
84
<div
85
className='fixed pointer-events-none z-50'
86
style={{
87
left: mouseState.x,
88
top: mouseState.y,
89
transform: 'translate(-50%, -50%)',
90
}}
91
>
92
<div className='w-8 h-8 bg-white rounded-full mix-blend-screen' />
93
</div>
94
</>
95
)}
96
97
<div className='flex flex-col items-center justify-center h-full gap-6'>
98
{['circle', 'spiral', 'grid'].map((patternType) => (
99
<button
100
key={patternType}
101
className={`px-6 py-3 rounded-lg transition-all duration-300 ${
102
pattern === patternType
103
? 'bg-purple-600 text-white'
104
: 'bg-purple-600/30 text-purple-200'
105
}`}
106
onClick={() => setPattern(patternType)}
107
>
108
{patternType.charAt(0).toUpperCase() + patternType.slice(1)} Pattern
109
</button>
110
))}
111
</div>
112
</div>
113
);
114
};
115
116
export default MultiCursor;
117

useMouse

hooks/useMouse.ts
1
'use client';
2
import { type RefObject, useLayoutEffect, useRef, useState } from 'react';
3
4
interface MouseState {
5
x: number | null;
6
y: number | null;
7
elementX: number | null;
8
elementY: number | null;
9
elementPositionX: number | null;
10
elementPositionY: number | null;
11
}
12
13
export function useMouse(): [MouseState, RefObject<HTMLDivElement>] {
14
const [state, setState] = useState<MouseState>({
15
x: null,
16
y: null,
17
elementX: null,
18
elementY: null,
19
elementPositionX: null,
20
elementPositionY: null,
21
});
22
23
const ref = useRef<HTMLDivElement | null>(null);
24
25
useLayoutEffect(() => {
26
const handleMouseMove = (event: MouseEvent) => {
27
const newState: Partial<MouseState> = {
28
x: event.pageX,
29
y: event.pageY,
30
};
31
32
if (ref.current instanceof Element) {
33
const { left, top } = ref.current.getBoundingClientRect();
34
const elementPositionX = left + window.scrollX;
35
const elementPositionY = top + window.scrollY;
36
const elementX = event.pageX - elementPositionX;
37
const elementY = event.pageY - elementPositionY;
38
39
newState.elementX = elementX;
40
newState.elementY = elementY;
41
newState.elementPositionX = elementPositionX;
42
newState.elementPositionY = elementPositionY;
43
}
44
45
setState((s) => ({
46
...s,
47
...newState,
48
}));
49
};
50
51
document.addEventListener('mousemove', handleMouseMove);
52
53
return () => {
54
document.removeEventListener('mousemove', handleMouseMove);
55
};
56
}, []);
57
58
return [state, ref];
59
}