Skip to content

Commit

Permalink
refactor: optimize perf and fix issue magicuidesign#339
Browse files Browse the repository at this point in the history
  • Loading branch information
notREKASH committed Oct 9, 2024
1 parent 9ef8b96 commit 9160e84
Showing 1 changed file with 98 additions and 76 deletions.
174 changes: 98 additions & 76 deletions registry/default/magicui/particles.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"use client";

import { cn } from "@/lib/utils";
import React, { useEffect, useRef, useState } from "react";
import React, {
useEffect,
useRef,
useState,
useMemo,
useCallback,
} from "react";

interface MousePosition {
x: number;
Expand Down Expand Up @@ -40,6 +46,7 @@ interface ParticlesProps {
vx?: number;
vy?: number;
}

function hexToRgb(hex: string): number[] {
hex = hex.replace("#", "");

Expand Down Expand Up @@ -77,45 +84,7 @@ const Particles: React.FC<ParticlesProps> = ({
const canvasSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 });
const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1;

useEffect(() => {
if (canvasRef.current) {
context.current = canvasRef.current.getContext("2d");
}
initCanvas();
animate();
window.addEventListener("resize", initCanvas);

return () => {
window.removeEventListener("resize", initCanvas);
};
}, [color]);

useEffect(() => {
onMouseMove();
}, [mousePosition.x, mousePosition.y]);

useEffect(() => {
initCanvas();
}, [refresh]);

const initCanvas = () => {
resizeCanvas();
drawParticles();
};

const onMouseMove = () => {
if (canvasRef.current) {
const rect = canvasRef.current.getBoundingClientRect();
const { w, h } = canvasSize.current;
const x = mousePosition.x - rect.left - w / 2;
const y = mousePosition.y - rect.top - h / 2;
const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2;
if (inside) {
mouse.current.x = x;
mouse.current.y = y;
}
}
};
const rgb = useMemo(() => hexToRgb(color), [color]);

type Circle = {
x: number;
Expand All @@ -130,7 +99,7 @@ const Particles: React.FC<ParticlesProps> = ({
magnetism: number;
};

const resizeCanvas = () => {
const resizeCanvas = useCallback(() => {
if (canvasContainerRef.current && canvasRef.current && context.current) {
circles.current.length = 0;
canvasSize.current.w = canvasContainerRef.current.offsetWidth;
Expand All @@ -141,9 +110,9 @@ const Particles: React.FC<ParticlesProps> = ({
canvasRef.current.style.height = `${canvasSize.current.h}px`;
context.current.scale(dpr, dpr);
}
};
}, [dpr]);

const circleParams = (): Circle => {
const circleParams = useCallback((): Circle => {
const x = Math.floor(Math.random() * canvasSize.current.w);
const y = Math.floor(Math.random() * canvasSize.current.h);
const translateX = 0;
Expand All @@ -166,27 +135,28 @@ const Particles: React.FC<ParticlesProps> = ({
dy,
magnetism,
};
};
}, [size]);

const rgb = hexToRgb(color);
const drawCircle = useCallback(
(circle: Circle, update = false) => {
if (context.current) {
const { x, y, translateX, translateY, size, alpha } = circle;
context.current.translate(translateX, translateY);
context.current.beginPath();
context.current.arc(x, y, size, 0, 2 * Math.PI);
context.current.fillStyle = `rgba(${rgb.join(", ")}, ${alpha})`;
context.current.fill();
context.current.setTransform(dpr, 0, 0, dpr, 0, 0);

const drawCircle = (circle: Circle, update = false) => {
if (context.current) {
const { x, y, translateX, translateY, size, alpha } = circle;
context.current.translate(translateX, translateY);
context.current.beginPath();
context.current.arc(x, y, size, 0, 2 * Math.PI);
context.current.fillStyle = `rgba(${rgb.join(", ")}, ${alpha})`;
context.current.fill();
context.current.setTransform(dpr, 0, 0, dpr, 0, 0);

if (!update) {
circles.current.push(circle);
if (!update) {
circles.current.push(circle);
}
}
}
};
},
[dpr, rgb],
);

const clearContext = () => {
const clearContext = useCallback(() => {
if (context.current) {
context.current.clearRect(
0,
Expand All @@ -195,30 +165,35 @@ const Particles: React.FC<ParticlesProps> = ({
canvasSize.current.h,
);
}
};
}, []);

const drawParticles = () => {
const drawParticles = useCallback(() => {
clearContext();
const particleCount = quantity;
for (let i = 0; i < particleCount; i++) {
const circle = circleParams();
drawCircle(circle);
}
};
}, [quantity, circleParams, clearContext, drawCircle]);

const remapValue = (
value: number,
start1: number,
end1: number,
start2: number,
end2: number,
): number => {
const remapped =
((value - start1) * (end2 - start2)) / (end1 - start1) + start2;
return remapped > 0 ? remapped : 0;
};
const remapValue = useCallback(
(
value: number,
start1: number,
end1: number,
start2: number,
end2: number,
): number => {
const remapped =
((value - start1) * (end2 - start2)) / (end1 - start1) + start2;
return remapped > 0 ? remapped : 0;
},
[],
);

const animate = useCallback(() => {
if (!context.current) return;

const animate = () => {
clearContext();
circles.current.forEach((circle: Circle, i: number) => {
// Handle the alpha value
Expand Down Expand Up @@ -267,7 +242,54 @@ const Particles: React.FC<ParticlesProps> = ({
}
});
window.requestAnimationFrame(animate);
};
}, [
clearContext,
circleParams,
drawCircle,
ease,
remapValue,
staticity,
vx,
vy,
]);

const initCanvas = useCallback(() => {
resizeCanvas();
drawParticles();
}, [resizeCanvas, drawParticles]);

const onMouseMove = useCallback(() => {
if (canvasRef.current) {
const rect = canvasRef.current.getBoundingClientRect();
const { w, h } = canvasSize.current;
const x = mousePosition.x - rect.left - w / 2;
const y = mousePosition.y - rect.top - h / 2;
const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2;
if (inside) {
mouse.current.x = x;
mouse.current.y = y;
}
}
}, [mousePosition]);

useEffect(() => {
if (canvasRef.current) {
context.current = canvasRef.current.getContext("2d");
initCanvas();
animate();

if (refresh) {
window.addEventListener("resize", initCanvas);
return () => {
window.removeEventListener("resize", initCanvas);
};
}
}
}, [color, refresh, initCanvas, animate]);

useEffect(() => {
onMouseMove();
}, [mousePosition.x, mousePosition.y, onMouseMove]);

return (
<div className={cn("pointer-events-none", className)} ref={canvasContainerRef} aria-hidden="true">
Expand Down

0 comments on commit 9160e84

Please sign in to comment.