Last active
May 27, 2024 08:09
-
-
Save fibonacid/f52524c02debe4c89603614b30a5d4ae to your computer and use it in GitHub Desktop.
Mouse Cursor (React + GSAP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Container } from "./styles"; | |
import { | |
forwardRef, | |
PropsWithChildren, | |
useImperativeHandle, | |
useRef, | |
} from "react"; | |
export type CursorProps = PropsWithChildren<{ | |
className?: string; | |
width: number; | |
height: number; | |
}>; | |
export type CursorRef = { | |
moveTo: (x: number, y: number) => void; | |
} | null; | |
const styles = { | |
position: "absolute", | |
top: "-1000px", | |
left: "-1000px" | |
} | |
/** | |
* Renders an element that exposes an imperative API to move it around | |
* @example @/components/Cursor/Example.tsx | |
*/ | |
const Cursor = forwardRef<CursorRef, CursorProps>(function Cursor(props, ref) { | |
const { className, width, height, children } = props; | |
const container = useRef<HTMLSpanElement>(null); | |
useImperativeHandle(ref, () => ({ | |
moveTo(x, y) { | |
gsap.to(container.current, { | |
x: x - width * 0.5, | |
y: y - height * 0.5, | |
}); | |
}, | |
})); | |
return ( | |
<div style={styles} ref={container} className={className}> | |
{children} | |
</div> | |
); | |
}); | |
export default Cursor; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type IconProps = { | |
width: number; | |
height: number; | |
}; | |
const Icon: React.FC<IconProps> = (props) => { | |
const { width, height } = props; | |
// Cat and Mouse, lol | |
return <span style={{ width, height }}>🐈</span>; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Cursor, { CursorRef } from "./Cursor.tsx"; | |
import Icon from "./Icon.tsx"; | |
import { useRef } from "react"; | |
// Knowing width and height ahead of time improves performance | |
// because we can skip calling `element.getBoundingClientRect()` | |
const WIDTH = 40; | |
const HEIGHT = 40; | |
export const MouseCursor: React.FC = () => { | |
const ref = useRef<CursorRef>(null); | |
useEffect(() => { | |
// Enclose ref value under the current scope. | |
const api = ref.current; | |
if (api) { | |
const handleMouseMove = (event: MouseEvent) => { | |
// Call api to move the element based on mouse coordinates | |
api.moveTo(event.clientX, event.clientY); | |
}; | |
// Listen for mouse movement throught the whole document. | |
document.addEventListener("mousemove", handleMouseMove); | |
return () => { | |
// Cancel listener when component unmounts | |
document.removeEventListener("mousemove", handleMouseMove); | |
}; | |
} | |
}, []); | |
return ( | |
<Cursor ref={ref} width={WIDTH} height={HEIGHT}> | |
<Icon width={WIDTH} height={HEIGHT} /> | |
</Cursor> | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Cursor, { CursorRef } from "./Cursor.tsx"; | |
import Icon from "./Icon.tsx"; | |
import { useRef } from "react"; | |
// Knowing width and height ahead of time improves performance | |
// because we can skip calling `element.getBoundingClientRect()` | |
const WIDTH = 40; | |
const HEIGHT = 40; | |
const styles = { | |
width: "500px", | |
height: "500px" | |
} | |
/** | |
* Same thing but movement is restricted to a specific area. | |
*/ | |
export const MouseCursorWithBounds: React.FC = () => { | |
const ref = useRef<CursorRef>(null); | |
const handleMouseMove = useCallback<MouseEventHandler<HTMLDivElement>>( | |
(event) => { | |
const api = ref.current; | |
if (api) { | |
// Call api to move the element based on mouse coordinates | |
api.moveTo(event.clientX, event.clientY); | |
} | |
}, | |
[] | |
); | |
return ( | |
<div style={styles} onMouseMove={handleMouseMove}> | |
<Cursor ref={ref} width={WIDTH} height={HEIGHT}> | |
<Icon width={WIDTH} height={HEIGHT} /> | |
</Cursor> | |
</div> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment