Figmapedia
목록으로 돌아가기

아웃풋 이미지

이미지를 해당 영역에 넣어주세요

프롬프트

TIP

아래에 준비된 이미지 배경과 커스텀 커서 이미지를 적용해서 사이트를 꾸며보세요!

javascript
//interface Particle {
  x: number;
  y: number;
  vx: number;
  vy: number;
  life: number;
  size: number;
  color: string;
  decay: number;
}

//

import React, { useEffect, useRef } from 'react';
import { Particle } from '../types';

const SparkleCursor: React.FC = () => {
 const canvasRef = useRef<HTMLCanvasElement>(null);
 const particles = useRef<Particle[]>([]);
 const requestRef = useRef<number>(0);
 const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
 const lastMouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });

 // Configuration
 const GOLD_PALETTE = [
 'rgba(255, 215, 0, 1)', // Gold
 'rgba(255, 223, 0, 1)', // Golden Yellow
 'rgba(218, 165, 32, 1)', // Goldenrod
 'rgba(255, 250, 205, 1)', // Lemon Chiffon (bright highlight)
 ];

 const createParticle = (x: number, y: number): Particle => {
 const angle = Math.random() * Math.PI * 2;
 const speed = Math.random() * 0.5 + 0.2;
 const size = Math.random() * 3 + 1; // Size between 1 and 4
 
 return {
 x,
 y,
 vx: Math.cos(angle) * speed,
 vy: Math.sin(angle) * speed + 0.5, // Slight gravity
 life: 1.0,
 size,
 color: GOLD_PALETTE[Math.floor(Math.random() * GOLD_PALETTE.length)],
 decay: Math.random() * 0.02 + 0.015,
 };
 };

 const animate = (time: number) => {
 const canvas = canvasRef.current;
 if (!canvas) return;
 
 const ctx = canvas.getContext('2d');
 if (!ctx) return;

 // Clear canvas
 ctx.clearRect(0, 0, canvas.width, canvas.height);

 // Interpolate mouse movement to fill gaps if mouse moves too fast
 const dist = Math.hypot(mouseRef.current.x - lastMouseRef.current.x, mouseRef.current.y - lastMouseRef.current.y);
 const steps = Math.min(dist, 20); // Limit interpolation steps
 
 if (dist > 1) {
 for (let i = 0; i < steps; i++) {
 const t = i / steps;
 const x = lastMouseRef.current.x + (mouseRef.current.x - lastMouseRef.current.x) * t;
 const y = lastMouseRef.current.y + (mouseRef.current.y - lastMouseRef.current.y) * t;
 // Add randomness to spawn
 if (Math.random() > 0.5) {
 particles.current.push(createParticle(x + (Math.random() - 0.5) * 5, y + (Math.random() - 0.5) * 5));
 }
 }
 }
 lastMouseRef.current = { ...mouseRef.current };

 // Update and draw particles
 for (let i = particles.current.length - 1; i >= 0; i--) {
 const p = particles.current[i];
 
 // Physics
 p.x += p.vx;
 p.y += p.vy;
 p.life -= p.decay;

 // Draw
 if (p.life > 0) {
 ctx.beginPath();
 ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
 ctx.fillStyle = p.color.replace('1)', `${p.life})`); // Fade out alpha
 ctx.fill();
 
 // Optional: Add a "shine" cross for larger particles
 if (p.size > 2.5 && p.life > 0.5) {
 ctx.save();
 ctx.translate(p.x, p.y);
 ctx.rotate(time * 0.005);
 ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.8})`;
 ctx.fillRect(-p.size * 2, -0.5, p.size * 4, 1);
 ctx.fillRect(-0.5, -p.size * 2, 1, p.size * 4);
 ctx.restore();
 }

 } else {
 particles.current.splice(i, 1);
 }
 }

 requestRef.current = requestAnimationFrame(animate);
 };

 useEffect(() => {
 const canvas = canvasRef.current;
 if (!canvas) return;

 const handleResize = () => {
 canvas.width = window.innerWidth;
 canvas.height = window.innerHeight;
 };
 
 const handleMouseMove = (e: MouseEvent) => {
 mouseRef.current = { x: e.clientX, y: e.clientY };
 };

 window.addEventListener('resize', handleResize);
 window.addEventListener('mousemove', handleMouseMove);
 
 // Initial size
 handleResize();
 // Initialize last mouse position
 lastMouseRef.current = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
 mouseRef.current = { x: window.innerWidth / 2, y: window.innerHeight / 2 };

 requestRef.current = requestAnimationFrame(animate);

 return () => {
 window.removeEventListener('resize', handleResize);
 window.removeEventListener('mousemove', handleMouseMove);
 cancelAnimationFrame(requestRef.current);
 };
 // eslint-disable-next-line react-hooks/exhaustive-deps
 }, []);

 return (
 <canvas
 ref={canvasRef}
 className="pointer-events-none fixed inset-0 z-[9999]"
 style={{ touchAction: 'none' }}
 />
 );
};

export default SparkleCursor;