75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import styles from './CustomCursor.module.css';
|
|
|
|
export function CustomCursor() {
|
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
const [isPointer, setIsPointer] = useState(false);
|
|
const [isHidden, setIsHidden] = useState(true);
|
|
const [isClicking, setIsClicking] = useState(false);
|
|
|
|
const handleMouseMove = useCallback((e: MouseEvent) => {
|
|
setPosition({ x: e.clientX, y: e.clientY });
|
|
setIsHidden(false);
|
|
|
|
const target = e.target as HTMLElement;
|
|
const isClickable =
|
|
target.tagName === 'A' ||
|
|
target.tagName === 'BUTTON' ||
|
|
!!target.closest('a') ||
|
|
!!target.closest('button') ||
|
|
window.getComputedStyle(target).cursor === 'pointer';
|
|
|
|
setIsPointer(isClickable);
|
|
}, []);
|
|
|
|
const handleMouseDown = useCallback(() => setIsClicking(true), []);
|
|
const handleMouseUp = useCallback(() => setIsClicking(false), []);
|
|
const handleMouseLeave = useCallback(() => setIsHidden(true), []);
|
|
const handleMouseEnter = useCallback(() => setIsHidden(false), []);
|
|
|
|
useEffect(() => {
|
|
// Don't show custom cursor on touch devices
|
|
if (window.matchMedia('(hover: none)').matches) {
|
|
return;
|
|
}
|
|
|
|
document.addEventListener('mousemove', handleMouseMove);
|
|
document.addEventListener('mousedown', handleMouseDown);
|
|
document.addEventListener('mouseup', handleMouseUp);
|
|
document.addEventListener('mouseleave', handleMouseLeave);
|
|
document.addEventListener('mouseenter', handleMouseEnter);
|
|
|
|
return () => {
|
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
document.removeEventListener('mousedown', handleMouseDown);
|
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
document.removeEventListener('mouseleave', handleMouseLeave);
|
|
document.removeEventListener('mouseenter', handleMouseEnter);
|
|
};
|
|
}, [handleMouseMove, handleMouseDown, handleMouseUp, handleMouseLeave, handleMouseEnter]);
|
|
|
|
// Don't render on touch devices
|
|
if (typeof window !== 'undefined' && window.matchMedia('(hover: none)').matches) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
className={`${styles.cursor} ${isPointer ? styles.pointer : ''} ${isHidden ? styles.hidden : ''} ${isClicking ? styles.clicking : ''}`}
|
|
style={{
|
|
left: position.x,
|
|
top: position.y,
|
|
}}
|
|
/>
|
|
<div
|
|
className={`${styles.cursorDot} ${isHidden ? styles.hidden : ''}`}
|
|
style={{
|
|
left: position.x,
|
|
top: position.y,
|
|
}}
|
|
/>
|
|
</>
|
|
);
|
|
}
|