AnimationMeteorsMeteorsA simple meteor shower effect using Tailwind CSS and React.Preview Code tailwind.config.js/** @type {import('tailwindcss').Config} */ module.exports = { content: [ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}", "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { extend: { keyframes: { // ... meteor: { "0%": { transform: "rotate(215deg) translateX(0)", opacity: "0" }, "70%": { opacity: "100%" }, "100%": { transform: "rotate(215deg) translateX(-500px)", opacity: "", }, }, }, animation: { // ... meteor: "meteor 0.2s linear infinite", }, }, }, plugins: [], }; Meteors.tsx"use client"; import React, { useEffect, useState } from "react"; import clsx from "clsx"; import { twMerge } from "tailwind-merge"; const cn = (...args: any[]) => { return clsx(twMerge(...args)); }; interface MeteorsProps { number?: number; } export const Meteors = ({ number = 20 }: MeteorsProps) => { const [meteorStyles, setMeteorStyles] = useState<Array<React.CSSProperties>>( [], ); useEffect(() => { const styles = [...new Array(number)].map(() => ({ top: -5, left: Math.floor(Math.random() * window.innerWidth) + "px", animationDelay: Math.random() * 1 + 0.2 + "s", animationDuration: Math.floor(Math.random() * 8 + 2) + "s", })); setMeteorStyles(styles); }, [number]); return ( <> {[...meteorStyles].map((style, idx) => ( <span key={idx} className={cn( "pointer-events-none absolute left-1/2 top-1/2 h-0.5 w-0.5 rotate-[215deg] animate-meteor rounded-[9999px] bg-white shadow-[0_0_90px_10px_#ffffff10]", )} style={style} > <div className="pointer-events-none absolute top-1/2 -z-10 h-[1px] w-[50px] -translate-y-1/2 bg-gradient-to-r from-slate-500 to-transparent" /> </span> ))} </> ); }; Usage index.tsximport { Meteors } from "@/components/library/general/Meteors"; export default function MeteorsPage() { return ( <div> <Meteors number={20} /> </div> ); } Props PropTypeDefaultnumbernumber-Edit on GitHubLast updated on PreviousMagic TextNextParticles