Snowfall Animation
Snowfall Animation é um componente para React e Tailwind CSS, da biblioteca Bund UI, com licença MIT. Copia e cola no teu projeto.
Bund UI
MIT
other
O que é
Snowfall Animation é um componente para React e Tailwind CSS, da biblioteca Bund UI, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i next-themes
Licença
Este componente vem de Bund UI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Snowfall Animation
import { Button } from "@/components/ui/button";
import Snowfall from "./snowfall";
export default function SnowfallBackgroundExample() {
return (
<Snowfall className="flex aspect-4/2 items-center justify-center">
<div className="z-10 space-y-4 text-center lg:space-y-6">
<h4 className="text-2xl font-semibold text-black/80 lg:text-3xl dark:text-white/80">
Bundui Components
</h4>
<Button>Discover Excellence</Button>
</div>
</Snowfall>
);
}
"use client";
import React, { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { useTheme } from "next-themes";
interface Snowflake {
x: number;
y: number;
radius: number;
speed: number;
opacity: number;
wind: number;
amplitude: number;
frequency: number;
angle: number;
}
export default function SnowfallBackground({
children,
count = 100,
minRadius = 1,
maxRadius = 4,
minSpeed = 0.5,
maxSpeed = 2,
wind = 0.5,
className = ""
}: {
children: React.ReactNode;
count?: number;
minRadius?: number;
maxRadius?: number;
minSpeed?: number;
maxSpeed?: number;
wind?: number;
className?: string;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const { theme } = useTheme();
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const snowflakesRef = useRef<Snowflake[]>([]);
const animationFrameRef = useRef<number>(0);
const naturalDistribution = () => {
const r = Math.random();
if (r < 0.3) {
return 0.1 + Math.random() * 0.3;
} else if (r < 0.9) {
return 0.5 + Math.random() * 1.0;
} else {
return 1.6 + Math.random() * 1.4;
}
};
const initSnowflakes = (width: number, height: number) => {
const snowflakes: Snowflake[] = [];
const adjustedCount = Math.min(count, Math.floor((width * height) / 10000));
for (let i = 0; i < adjustedCount; i++) {
snowflakes.push({
x: Math.random() * width,
y: Math.random() * height,
radius: minRadius + Math.random() * (maxRadius - minRadius),
speed: minSpeed + Math.random() * (maxSpeed - minSpeed),
opacity: 0.3 + Math.random() * 0.7,
wind: (Math.random() - 0.5) * wind * 0.5,
amplitude: naturalDistribution(),
frequency: 0.001 + Math.random() * 0.008,
angle: Math.random() * Math.PI * 2
});
}
snowflakesRef.current = snowflakes;
};
useEffect(() => {
const handleResize = () => {
if (canvasRef.current && canvasRef.current.parentElement) {
const { width, height } = canvasRef.current.parentElement.getBoundingClientRect();
setDimensions({ width, height });
canvasRef.current.width = width;
canvasRef.current.height = height;
initSnowflakes(width, height);
}
};
handleResize();
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, []);
useEffect(() => {
if (!canvasRef.current || dimensions.width === 0 || dimensions.height === 0) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const animate = () => {
ctx.clearRect(0, 0, dimensions.width, dimensions.height);
const snowColor = theme === "dark" ? "rgba(255, 255, 255," : "rgba(220, 235, 255,";
snowflakesRef.current.forEach((flake) => {
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
ctx.fillStyle = `${snowColor} ${flake.opacity})`;
ctx.fill();
flake.y += flake.speed;
flake.angle += flake.frequency;
flake.x += flake.wind + Math.sin(flake.angle) * flake.amplitude;
if (flake.y > dimensions.height) {
flake.y = -flake.radius;
flake.x = Math.random() * dimensions.width;
flake.amplitude = naturalDistribution();
flake.frequency = 0.001 + Math.random() * 0.008;
}
if (flake.x > dimensions.width) {
flake.x = 0;
} else if (flake.x < 0) {
flake.x = dimensions.width;
}
});
animationFrameRef.current = requestAnimationFrame(animate);
};
animate();
return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, [dimensions, theme]);
return (
<div className={cn("relative w-full", className)}>
<canvas
ref={canvasRef}
className={`pointer-events-none absolute inset-0 ${className}`}
style={{ zIndex: 0 }}
/>
<div className="z-10">{children}</div>
</div>
);
}