Dashboard Temp Share Shortlinks Frames API

HTMLify

smooth-cursor.tsx
Views: 2 | Author: biisal
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
'use client';

import { FC, useEffect, useRef } from 'react';
import { motion, useSpring } from 'motion/react';

interface Position {
	x: number;
	y: number;
}

export interface SmoothCursorProps {
	cursor?: React.ReactNode;
	springConfig?: {
		damping: number;
		stiffness: number;
		mass: number;
		restDelta: number;
	};
}

const DefaultCursorSVG: FC = () => {
	return (
		<svg
			xmlns="http://www.w3.org/2000/svg"
			width={50}
			height={54}
			viewBox="0 0 50 54"
			fill="none"
			style={{ scale: 0.5 }}
		>
			<g filter="url(#filter0_d_91_7928)">
				<path
					d="M42.6817 41.1495L27.5103 6.79925C26.7269 5.02557 24.2082 5.02558 23.3927 6.79925L7.59814 41.1495C6.75833 42.9759 8.52712 44.8902 10.4125 44.1954L24.3757 39.0496C24.8829 38.8627 25.4385 38.8627 25.9422 39.0496L39.8121 44.1954C41.6849 44.8902 43.4884 42.9759 42.6817 41.1495Z"
					fill="black"
				/>
				<path
					d="M43.7146 40.6933L28.5431 6.34306C27.3556 3.65428 23.5772 3.69516 22.3668 6.32755L6.57226 40.6778C5.3134 43.4156 7.97238 46.298 10.803 45.2549L24.7662 40.109C25.0221 40.0147 25.2999 40.0156 25.5494 40.1082L39.4193 45.254C42.2261 46.2953 44.9254 43.4347 43.7146 40.6933Z"
					stroke="white"
					strokeWidth={2.25825}
				/>
			</g>
			<defs>
				<filter
					id="filter0_d_91_7928"
					x={0.602397}
					y={0.952444}
					width={49.0584}
					height={52.428}
					filterUnits="userSpaceOnUse"
					colorInterpolationFilters="sRGB"
				>
					<feFlood floodOpacity={0} result="BackgroundImageFix" />
					<feColorMatrix
						in="SourceAlpha"
						type="matrix"
						values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
						result="hardAlpha"
					/>
					<feOffset dy={2.25825} />
					<feGaussianBlur stdDeviation={2.25825} />
					<feComposite in2="hardAlpha" operator="out" />
					<feColorMatrix
						type="matrix"
						values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"
					/>
					<feBlend
						mode="normal"
						in2="BackgroundImageFix"
						result="effect1_dropShadow_91_7928"
					/>
					<feBlend
						mode="normal"
						in="SourceGraphic"
						in2="effect1_dropShadow_91_7928"
						result="shape"
					/>
				</filter>
			</defs>
		</svg>
	);
};

export function SmoothCursor({
	cursor = <DefaultCursorSVG />,
	springConfig = {
		damping: 45,
		stiffness: 400,
		mass: 1,
		restDelta: 0.001,
	},
}: SmoothCursorProps) {
	const lastMousePos = useRef<Position>({ x: 0, y: 0 });
	const velocity = useRef<Position>({ x: 0, y: 0 });
	const lastUpdateTime = useRef(Date.now());
	const previousAngle = useRef(0);
	const accumulatedRotation = useRef(0);

	const cursorX = useSpring(0, springConfig);
	const cursorY = useSpring(0, springConfig);
	const rotation = useSpring(0, {
		...springConfig,
		damping: 60,
		stiffness: 300,
	});
	const scale = useSpring(1, {
		...springConfig,
		stiffness: 500,
		damping: 35,
	});

	useEffect(() => {
		const updateVelocity = (currentPos: Position) => {
			const currentTime = Date.now();
			const deltaTime = currentTime - lastUpdateTime.current;

			if (deltaTime > 0) {
				velocity.current = {
					x: (currentPos.x - lastMousePos.current.x) / deltaTime,
					y: (currentPos.y - lastMousePos.current.y) / deltaTime,
				};
			}

			lastUpdateTime.current = currentTime;
			lastMousePos.current = currentPos;
		};

		const smoothMouseMove = (e: MouseEvent) => {
			const currentPos = { x: e.clientX, y: e.clientY };
			updateVelocity(currentPos);

			const speed = Math.sqrt(
				Math.pow(velocity.current.x, 2) + Math.pow(velocity.current.y, 2),
			);

			cursorX.set(currentPos.x);
			cursorY.set(currentPos.y);

			if (speed > 0.1) {
				const currentAngle =
					Math.atan2(velocity.current.y, velocity.current.x) * (180 / Math.PI) +
					90;

				let angleDiff = currentAngle - previousAngle.current;
				if (angleDiff > 180) angleDiff -= 360;
				if (angleDiff < -180) angleDiff += 360;
				accumulatedRotation.current += angleDiff;
				rotation.set(accumulatedRotation.current);
				previousAngle.current = currentAngle;

				scale.set(0.95);

				const timeout = setTimeout(() => {
					scale.set(1);
				}, 150);

				return () => clearTimeout(timeout);
			}
		};

		let rafId: number;
		const throttledMouseMove = (e: MouseEvent) => {
			if (rafId) return;

			rafId = requestAnimationFrame(() => {
				smoothMouseMove(e);
				rafId = 0;
			});
		};

		document.body.style.cursor = 'none';
		window.addEventListener('mousemove', throttledMouseMove);

		return () => {
			window.removeEventListener('mousemove', throttledMouseMove);
			document.body.style.cursor = 'auto';
			if (rafId) cancelAnimationFrame(rafId);
		};
	}, [cursorX, cursorY, rotation, scale]);

	return (
		<motion.div
			style={{
				position: 'fixed',
				left: cursorX,
				top: cursorY,
				translateX: '-50%',
				translateY: '-50%',
				rotate: rotation,
				scale: scale,
				zIndex: 100,
				pointerEvents: 'none',
				willChange: 'transform',
			}}
			initial={{ scale: 0 }}
			animate={{ scale: 1 }}
			transition={{
				type: 'spring',
				stiffness: 400,
				damping: 30,
			}}
		>
			{cursor}
		</motion.div>
	);
}