3d Cursor Effect

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

1
'use client';
2
3
import { useState, useEffect } from 'react';
4
import { useMouse } from '@/hooks/use-mouse';
5
6
const ThreeDCursor = () => {
7
const [mouseState, ref] = useMouse();
8
const [rotation, setRotation] = useState({ x: 0, y: 0 });
9
const [isHovering, setIsHovering] = useState(false);
10
11
useEffect(() => {
12
if (mouseState.x && mouseState.y) {
13
const centerX = window.innerWidth / 2;
14
const centerY = window.innerHeight / 2;
15
16
const rotateX = ((mouseState.y - centerY) / centerY) * 30;
17
const rotateY = ((mouseState.x - centerX) / centerX) * 30;
18
19
setRotation({ x: rotateX, y: rotateY });
20
}
21
}, [mouseState.x, mouseState.y]);
22
23
return (
24
<div className='relative w-full h-full overflow-hidden' ref={ref}>
25
{mouseState.x !== null && mouseState.y !== null && (
26
<div
27
className='fixed pointer-events-none z-50 transition-transform duration-100'
28
style={{
29
left: mouseState.x,
30
top: mouseState.y,
31
transform: `translate(-50%, -50%) perspective(1000px)
32
rotateX(${rotation.x}deg) rotateY(${rotation.y}deg)
33
scale(${isHovering ? 1.5 : 1})`,
34
}}
35
>
36
{/* 3D Cursor Structure */}
37
<div className='relative w-12 h-12'>
38
{/* Front face */}
39
<div
40
className='absolute inset-0 bg-indigo-500 rounded-lg mix-blend-screen'
41
style={{
42
transform: 'translateZ(6px)',
43
boxShadow: '0 0 20px rgba(99, 102, 241, 0.5)',
44
}}
45
/>
46
47
{/* Back face */}
48
<div
49
className='absolute inset-0 bg-indigo-700 rounded-lg mix-blend-screen'
50
style={{
51
transform: 'translateZ(-6px)',
52
}}
53
/>
54
55
{/* Side faces */}
56
{[...Array(4)].map((_, index) => (
57
<div
58
key={index}
59
className='absolute inset-0 bg-indigo-600 mix-blend-screen'
60
style={{
61
transform: `rotateY(${index * 90}deg) translateZ(6px)`,
62
width: '12px',
63
left: index % 2 === 0 ? 0 : 'auto',
64
right: index % 2 === 1 ? 0 : 'auto',
65
}}
66
/>
67
))}
68
</div>
69
70
{/* Shadow */}
71
<div
72
className='absolute rounded-full bg-black/30 blur-md'
73
style={{
74
width: '48px',
75
height: '8px',
76
top: '100%',
77
left: '50%',
78
transform: 'translate(-50%, -4px) rotateX(90deg)',
79
}}
80
/>
81
</div>
82
)}
83
84
<div className='flex items-center justify-center h-full gap-8'>
85
<button
86
className='px-8 py-4 bg-indigo-600 text-white rounded-lg transition-all duration-300'
87
style={{
88
transform: `perspective(1000px)
89
rotateX(${-rotation.x * 0.5}deg)
90
rotateY(${-rotation.y * 0.5}deg)`,
91
}}
92
onMouseEnter={() => setIsHovering(true)}
93
onMouseLeave={() => setIsHovering(false)}
94
>
95
3D Hover Effect
96
</button>
97
</div>
98
</div>
99
);
100
};
101
102
export default ThreeDCursor;
103

useMouse

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
}