Todos os componentes

Meteor Shower Animation

Meteor Shower Animation é um componente de efeito para React e Tailwind CSS, da biblioteca Bund UI, com licença MIT. Copia e cola no teu projeto.

Bund UI MIT effects

O que é

Meteor Shower Animation é um componente de efeito 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.

Abrir no catálogo

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.

Meteor Shower Animation

examples/motion/backgrounds/meteor-shower-animation/01/meteor-shower.tsx
"use client";

import React, { useEffect, useRef, useState } from "react";
import { useTheme } from "next-themes";
import { cn } from "@/lib/utils";

interface Meteor {
  x: number;
  y: number;
  size: number;
  speed: number;
  angle: number;
  opacity: number;
  tail: { x: number; y: number }[];
  tailLength: number;
}

interface ThemeColors {
  background: string;
  meteorHead: string;
  meteorTailStart: string;
  meteorTailMiddle: string;
  meteorTailEnd: string;
}

export default function MeteorShower({
  children,
  className
}: {
  children: React.ReactNode;
  className?: string;
}) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
  const meteorsRef = useRef<Meteor[]>([]);
  const animationRef = useRef<number>(0);
  const { theme, resolvedTheme } = useTheme();
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  useEffect(() => {
    const updateDimensions = () => {
      if (typeof window !== "undefined") {
        setDimensions({
          width: window.innerWidth,
          height: window.innerHeight
        });
      }
    };

    updateDimensions();
    window.addEventListener("resize", updateDimensions);

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

  useEffect(() => {
    if (!canvasRef.current || dimensions.width === 0 || dimensions.height === 0 || !mounted) return;

    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    canvas.width = dimensions.width;
    canvas.height = dimensions.height;

    const isDark =
      resolvedTheme === "dark" ||
      document.documentElement.classList.contains("dark") ||
      (theme === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);

    const themeColors: ThemeColors = isDark
      ? {
          background: "#000000",
          meteorHead: "rgba(255, 255, 255, 1)",
          meteorTailStart: "rgba(255, 255, 255, 1)",
          meteorTailMiddle: "rgba(255, 240, 200, 0.8)",
          meteorTailEnd: "rgba(255, 200, 100, 0.1)"
        }
      : {
          background: "#ffffff",
          meteorHead: "rgba(70, 90, 120, 1)",
          meteorTailStart: "rgba(70, 90, 120, 1)",
          meteorTailMiddle: "rgba(100, 120, 150, 0.8)",
          meteorTailEnd: "rgba(130, 150, 180, 0.1)"
        };

    const createMeteor = (): Meteor => {
      const positionFactor = Math.random();
      let x, y;

      if (positionFactor < 0.25) {
        x = -20;
        y = dimensions.height * Math.random() * 0.7;
      } else if (positionFactor < 0.5) {
        x = dimensions.width * Math.random();
        y = -20;
      } else if (positionFactor < 0.75) {
        x = dimensions.width + 20;
        y = dimensions.height * Math.random() * 0.7;
      } else {
        x = dimensions.width * 0.3 + Math.random() * dimensions.width * 0.4;
        y = -20;
      }

      const size = 1 + Math.random() * 10;
      const speed = 3;
      const angle = Math.PI / 4;
      const tailLength = 15;

      return {
        x,
        y,
        size,
        speed,
        angle,
        opacity: 0.7 + Math.random() * 0.3,
        tail: [],
        tailLength
      };
    };

    meteorsRef.current = Array.from({ length: 5 }, createMeteor);

    const animate = () => {
      ctx.fillStyle = themeColors.background;
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      meteorsRef.current.forEach((meteor, index) => {
        meteor.x += Math.cos(meteor.angle) * meteor.speed;
        meteor.y += Math.sin(meteor.angle) * meteor.speed;

        meteor.tail.unshift({ x: meteor.x, y: meteor.y });

        if (meteor.tail.length > meteor.tailLength) {
          meteor.tail.pop();
        }

        if (meteor.tail.length > 1) {
          ctx.beginPath();

          const gradient = ctx.createLinearGradient(
            meteor.tail[0].x,
            meteor.tail[0].y,
            meteor.tail[meteor.tail.length - 1].x,
            meteor.tail[meteor.tail.length - 1].y
          );

          gradient.addColorStop(0, themeColors.meteorTailStart.replace("1)", `${meteor.opacity})`));
          gradient.addColorStop(
            0.3,
            themeColors.meteorTailMiddle.replace("0.8)", `${meteor.opacity * 0.8})`)
          );
          gradient.addColorStop(
            1,
            themeColors.meteorTailEnd.replace("0.1)", `${meteor.opacity * 0.1})`)
          );

          ctx.strokeStyle = gradient;
          ctx.lineWidth = meteor.size;

          ctx.moveTo(meteor.tail[0].x, meteor.tail[0].y);
          for (let i = 1; i < meteor.tail.length; i++) {
            ctx.lineTo(meteor.tail[i].x, meteor.tail[i].y);
          }

          ctx.stroke();
        }

        ctx.beginPath();
        ctx.arc(meteor.x, meteor.y, meteor.size / 2, 0, Math.PI * 2);
        ctx.fillStyle = themeColors.meteorHead.replace("1)", `${meteor.opacity})`);
        ctx.fill();

        if (meteor.y > dimensions.height || meteor.x < -50 || meteor.x > dimensions.width + 50) {
          meteorsRef.current[index] = createMeteor();
        }
      });

      if (Math.random() < 0.02 && meteorsRef.current.length < 12) {
        meteorsRef.current.push(createMeteor());
      }

      animationRef.current = requestAnimationFrame(animate);
    };

    animate();

    return () => {
      cancelAnimationFrame(animationRef.current);
    };
  }, [dimensions, theme, resolvedTheme, mounted]);

  return (
    <div className={cn("relative w-full", className)}>
      <canvas
        ref={canvasRef}
        className="absolute h-full w-full"
        style={{ display: dimensions.width > 0 ? "block" : "none" }}
      />
      <div className="z-10">{children}</div>
    </div>
  );
}
examples/motion/backgrounds/meteor-shower-animation/01/page.tsx
import { Button } from "@/components/ui/button";
import MeteorShower from "./meteor-shower";

export default function MeteorShowerAnimationExample() {
  return (
    <MeteorShower 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>
    </MeteorShower>
  );
}

Componentes parecidos