lightswind
Version:
A professionally designed animate react component library & templates market that brings together functionality, accessibility, and beautiful aesthetics for modern applications.
125 lines (124 loc) • 5.52 kB
JavaScript
"use client"; // only if using Next.js App Router with React Server Components
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
import { useEffect, useRef, useCallback } from "react";
import { particlesCursor } from "threejs-toys";
const cn = (...classes) => classes.filter(Boolean).join(" ");
const defaultConfig = {
colors: [0x00fffc, 0x0000ff],
color: 0xff0000,
coordScale: 0.5,
noiseIntensity: 0.005,
noiseTimeCoef: 0.0001,
pointSize: 2,
pointDecay: 0.0025,
sleepRadiusX: 250,
sleepRadiusY: 250,
sleepTimeCoefX: 0.001,
sleepTimeCoefY: 0.002,
gpgpuSize: 512,
};
const MagicCursor = ({ target, config = {}, enabled = true, className, clickInteraction = true, resetDuration = 2000, showOnMobile = false, interactionColors = [
0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff,
], children, }) => {
const containerRef = useRef(null);
const particlesCursorRef = useRef(null);
const resetTimeoutRef = useRef();
const resizeObserverRef = useRef(null);
const isMobile = () => {
if (typeof navigator === "undefined" || !navigator.userAgent)
return false;
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
};
const initializeParticles = useCallback(() => {
if (!enabled || (isMobile() && !showOnMobile)) {
particlesCursorRef.current?.destroy?.();
resizeObserverRef.current?.disconnect?.();
return;
}
const targetElement = typeof target === "string"
? document.querySelector(target)
: target || containerRef.current;
if (!targetElement)
return;
const mergedConfig = { ...defaultConfig, ...config };
particlesCursorRef.current = particlesCursor({
el: targetElement,
...mergedConfig,
});
const updateCanvasSize = () => {
const width = targetElement.clientWidth;
const height = targetElement.clientHeight;
const canvas = targetElement.querySelector("canvas");
if (canvas) {
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.display = "block";
}
particlesCursorRef.current?.renderer?.setSize(width, height);
if (particlesCursorRef.current?.camera && width > 0 && height > 0) {
particlesCursorRef.current.camera.aspect = width / height;
particlesCursorRef.current.camera.updateProjectionMatrix();
}
};
updateCanvasSize();
const observer = new ResizeObserver(updateCanvasSize);
observer.observe(targetElement);
resizeObserverRef.current = observer;
const handleMouseMove = (event) => {
const rect = targetElement.getBoundingClientRect();
const x = (event.clientX - rect.left) / rect.width;
const y = 1 - (event.clientY - rect.top) / rect.height;
particlesCursorRef.current?.uniforms?.uMousePos?.value?.set(x, y);
};
const handleClick = () => {
if (!clickInteraction || !particlesCursorRef.current)
return;
const randomColor = interactionColors[Math.floor(Math.random() * interactionColors.length)];
const uniforms = particlesCursorRef.current.uniforms;
uniforms?.uColor?.value?.set(randomColor);
uniforms.uCoordScale.value = 0.001 + Math.random() * 2;
uniforms.uNoiseIntensity.value = 0.0001 + Math.random() * 0.001;
uniforms.uPointSize.value = 1 + Math.random() * 10;
if (resetTimeoutRef.current)
clearTimeout(resetTimeoutRef.current);
resetTimeoutRef.current = setTimeout(() => {
const resetConfig = { ...defaultConfig, ...config };
uniforms?.uColor?.value?.set(resetConfig.color || 0xff0000);
uniforms.uCoordScale.value = resetConfig.coordScale || 0.5;
uniforms.uNoiseIntensity.value = resetConfig.noiseIntensity || 0.005;
uniforms.uPointSize.value = resetConfig.pointSize || 2;
}, resetDuration);
};
targetElement.addEventListener("mousemove", handleMouseMove);
if (clickInteraction)
targetElement.addEventListener("click", handleClick);
return () => {
targetElement.removeEventListener("mousemove", handleMouseMove);
targetElement.removeEventListener("click", handleClick);
clearTimeout(resetTimeoutRef.current);
resizeObserverRef.current?.disconnect?.();
particlesCursorRef.current?.destroy?.();
};
}, [
target,
config,
enabled,
showOnMobile,
clickInteraction,
resetDuration,
interactionColors,
]);
useEffect(() => {
const cleanup = initializeParticles();
return () => cleanup?.();
}, [initializeParticles]);
if (!enabled || (isMobile() && !showOnMobile)) {
return _jsx(_Fragment, { children: children });
}
return (_jsx("div", { ref: containerRef, className: cn("relative w-full h-full overflow-hidden", className), style: {
position: "relative",
cursor: clickInteraction ? "pointer" : "default",
minHeight: "100px",
}, children: children }));
};
export default MagicCursor;