Files
ragusaitweb/src/components/layout/FancyCursor.tsx
google-labs-jules[bot] 7c0a0bbec2 perf(FancyCursor): optimize mousemove handler with ticking pattern
- Replaces the `cancelAnimationFrame` pattern with a boolean ticking flag to reduce function allocation and RAF overhead on high-frequency mousemove events.
- Uses closure variables for coordinates to ensure the latest position is used in the animation frame.
- Improves performance of the custom cursor on high-refresh-rate input devices.
2026-01-25 01:37:50 +00:00

95 lines
4.0 KiB
TypeScript

import { memo, useEffect, useRef, useState } from 'react';
import './FancyCursor.css';
// This is the new, more performant implementation for the custom cursor.
// It uses requestAnimationFrame for smooth movement and direct DOM manipulation for speed.
export const FancyCursor = memo(() => {
const cursorRef = useRef<HTMLDivElement>(null);
const [isTouch, setIsTouch] = useState(false);
useEffect(() => {
// Check for touch devices once at the beginning. If it's a touch device,
// the custom cursor will not be rendered.
if (typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0)) {
setIsTouch(true);
return;
}
const cursor = cursorRef.current;
if (!cursor) return;
let animationFrameId: number;
const updateCursorState = (el: Element | null) => {
const classList = cursor.classList;
// Coercing to boolean with `!!` is a micro-optimization.
const isResize = !!el?.closest('#custom-resize-handle');
const isText = !!el?.closest('input[type="text"], input[type="email"], textarea, [contenteditable="true"]');
const isLink = !!el?.closest('a, button, [role="button"], input[type="submit"], input[type="button"]');
// These classes are toggled based on the hovered element.
// The actual visual styles are defined in your CSS files.
classList.toggle('resize-hover', isResize);
classList.toggle('text-hover', isText && !isResize);
classList.toggle('link-hover', isLink && !isText && !isResize);
};
const handleMouseOver = (e: MouseEvent) => {
updateCursorState(e.target as Element);
};
// Optimization: Use a ticking flag and closure variables to prevent
// excessive object creation and RAF churn during high-frequency events.
let ticking = false;
let clientX = 0;
let clientY = 0;
const updateCursor = () => {
// The offset issue was caused by positioning the cursor's top-left corner
// at the mouse coordinates. To fix this, `translate(-50%, -50%)` is added.
// This shifts the cursor element by half its own width and height,
// which effectively centers it on the pointer without affecting the visuals.
cursor.style.transform = `translate3d(${clientX}px, ${clientY}px, 0) translate(-50%, -50%)`;
ticking = false;
};
const handleMouseMove = (e: MouseEvent) => {
clientX = e.clientX;
clientY = e.clientY;
if (!ticking) {
animationFrameId = requestAnimationFrame(updateCursor);
ticking = true;
}
};
// Using a passive event listener can improve scrolling performance.
window.addEventListener('mousemove', handleMouseMove, { passive: true });
window.addEventListener('mouseover', handleMouseOver, { passive: true });
// The cleanup function removes the event listener when the component unmounts
// to prevent memory leaks.
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseover', handleMouseOver);
cancelAnimationFrame(animationFrameId);
};
}, []); // The empty dependency array ensures this effect runs only once.
if (isTouch) {
return null;
}
return (
// `will-change` is a hint to the browser to optimize for transform changes.
<div ref={cursorRef} id="fancy-cursor" style={{ willChange: 'transform' }}>
<div className="fancy-cursor-dot" />
{/* SVG updated to use currentColor to inherit from CSS */}
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 19L19 5M5 19L9 19M5 19L5 15M19 5L15 5M19 5L19 9" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
);
});