Files

110 lines
3.3 KiB
TypeScript

import React, { useState, useEffect } from 'react';
const Confetti = () => {
const [ shouldGenerate, setShouldGenerate ] = useState(true);
useEffect(() => {
const timer = setTimeout(() => {
setShouldGenerate(false);
}, 5000);
return () => clearTimeout(timer);
}, []);
const getRandomShape = () => {
const shapes = [
// Circle
{ borderRadius: '50%' },
// Triangle
{ clipPath: 'polygon(50% 0%, 0% 100%, 100% 100%)' },
// Star
{ clipPath: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)' },
// Diamond
{ clipPath: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)' },
// Square (default)
{ borderRadius: '0' },
];
return shapes[Math.floor(Number(Math.random()) * shapes.length)];
};
const createSplash = (centerX: number, centerY: number, delay: number) => {
const confettiCount = 100;
return Array.from({ length: confettiCount }).map((_, i) => {
const angle = Number(Math.random()) * 360;
const distance = Number(Math.random()) * 200 + 100;
const shape = getRandomShape();
return {
id: `${ centerX }-${ centerY }-${ i }`,
left: `${ centerX }%`,
top: `${ centerY }%`,
angle,
distance,
animationDuration: `${ Number(Math.random()) * 1 + 1 }s`,
animationDelay: `${ delay }s`,
size: `${ Number(Math.random()) * 10 + 5 }px`,
color: `hsl(${ Number(Math.random()) * 360 }, 100%, 50%)`,
transform: `translate(
calc(-50% + ${ Math.cos(angle * Math.PI / 180) * distance }px),
calc(-50% + ${ Math.sin(angle * Math.PI / 180) * distance }px)
)`,
shape,
};
});
};
const splashes = shouldGenerate ? [
createSplash(50, 50, 0), // center
createSplash(20, 30, Number(Math.random()) * 3), // top left
createSplash(80, 30, Number(Math.random()) * 3), // top right
createSplash(20, 70, Number(Math.random()) * 3), // bottom left
createSplash(80, 70, Number(Math.random()) * 3), // bottom right
].flat() : [];
return (
<>
<style>
{ `
@keyframes splash {
0% {
transform: translate(-50%, -50%) scale(0);
opacity: 0;
}
1% {
opacity: 1;
}
80% {
opacity: 0.9;
}
100% {
transform: var(--final-transform) scale(1);
opacity: 0;
}
}
` }
</style>
<div className="fixed top-0 left-0 w-full h-full pointer-events-none z-[9999]">
{ splashes.map((piece) => (
<div
key={ piece.id }
className="absolute"
style={{
left: piece.left,
top: piece.top,
width: piece.size,
height: piece.size,
backgroundColor: piece.color,
opacity: 0,
animation: `splash ${ piece.animationDuration } ease-out ${ piece.animationDelay } forwards`,
transformOrigin: 'center',
'--final-transform': piece.transform,
...piece.shape,
} as unknown as React.CSSProperties}
/>
)) }
</div>
</>
);
};
export default Confetti;