UI Components Easy
Meteors
Animated shooting stars and meteors falling diagonally across the screen with glowing trails, random positions, and staggered animation delays.
Open in Lab
MCP
css javascript vue svelte
Targets: TS JS HTML React Vue Svelte
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
min-height: 100vh;
background: #0a0a0a;
overflow: hidden;
}
.meteors-wrapper {
position: relative;
width: 100%;
height: 100vh;
display: grid;
place-items: center;
background: linear-gradient(180deg, #0a0a0a 0%, #0f172a 50%, #0a0a0a 100%);
overflow: hidden;
}
/* Subtle star field background */
.stars-bg {
position: absolute;
inset: 0;
background-image: radial-gradient(
1px 1px at 10% 20%,
rgba(255, 255, 255, 0.4) 0%,
transparent 100%
), radial-gradient(1px 1px at 30% 60%, rgba(255, 255, 255, 0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 50% 10%, rgba(255, 255, 255, 0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 70% 40%, rgba(255, 255, 255, 0.2) 0%, transparent 100%),
radial-gradient(1px 1px at 90% 80%, rgba(255, 255, 255, 0.4) 0%, transparent 100%),
radial-gradient(1px 1px at 15% 85%, rgba(255, 255, 255, 0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 45% 75%, rgba(255, 255, 255, 0.2) 0%, transparent 100%),
radial-gradient(1px 1px at 65% 15%, rgba(255, 255, 255, 0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 85% 55%, rgba(255, 255, 255, 0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 25% 45%, rgba(255, 255, 255, 0.4) 0%, transparent 100%);
}
.meteors-container {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.meteor {
position: absolute;
transform: rotate(215deg);
width: var(--meteor-length, 150px);
height: 1px;
border-radius: 999px;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(148, 163, 184, 0.3) 20%,
rgba(255, 255, 255, 0.8) 100%
);
animation: meteorFall var(--meteor-duration, 3s) linear infinite;
animation-delay: var(--meteor-delay, 0s);
opacity: 0;
}
/* Glowing head of the meteor */
.meteor::before {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 6px 2px rgba(255, 255, 255, 0.8), 0 0 12px 4px rgba(148, 163, 184, 0.4);
}
/* Wider glow trail */
.meteor::after {
content: "";
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
width: 60%;
height: 3px;
border-radius: 999px;
background: linear-gradient(
90deg,
transparent 0%,
rgba(148, 163, 184, 0.08) 40%,
rgba(255, 255, 255, 0.15) 100%
);
filter: blur(1px);
}
@keyframes meteorFall {
0% {
opacity: 0;
transform: rotate(215deg) translateX(0);
}
5% {
opacity: 1;
}
70% {
opacity: 1;
}
100% {
opacity: 0;
transform: rotate(215deg) translateX(-120vh);
}
}
.meteors-content {
position: relative;
z-index: 10;
text-align: center;
color: #f1f5f9;
pointer-events: none;
}
.meteors-title {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e2e8f0 0%, #94a3b8 50%, #64748b 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
}
.meteors-subtitle {
font-size: clamp(0.875rem, 2vw, 1.125rem);
color: rgba(148, 163, 184, 0.8);
font-weight: 400;
}// Meteors — animated shooting stars falling diagonally
(function () {
"use strict";
const container = document.getElementById("meteors-container");
if (!container) return;
const METEOR_COUNT = 20;
const MIN_DURATION = 2;
const MAX_DURATION = 6;
const MIN_LENGTH = 80;
const MAX_LENGTH = 200;
function randomRange(min, max) {
return Math.random() * (max - min) + min;
}
for (let i = 0; i < METEOR_COUNT; i++) {
const meteor = document.createElement("div");
meteor.className = "meteor";
// Random position in the top-right quadrant
const top = randomRange(-10, 80);
const left = randomRange(10, 110);
const duration = randomRange(MIN_DURATION, MAX_DURATION);
const delay = randomRange(0, 10);
const length = randomRange(MIN_LENGTH, MAX_LENGTH);
meteor.style.top = top + "%";
meteor.style.left = left + "%";
meteor.style.setProperty("--meteor-duration", duration + "s");
meteor.style.setProperty("--meteor-delay", delay + "s");
meteor.style.setProperty("--meteor-length", length + "px");
container.appendChild(meteor);
}
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Meteors</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="meteors-wrapper">
<div id="meteors-container" class="meteors-container"></div>
<div class="stars-bg"></div>
<div class="meteors-content">
<h1 class="meteors-title">Meteors</h1>
<p class="meteors-subtitle">Shooting stars streaking across the night sky</p>
</div>
</div>
<script src="script.js"></script>
</body>
</html>import { useMemo } from "react";
interface MeteorsProps {
count?: number;
minDuration?: number;
maxDuration?: number;
minLength?: number;
maxLength?: number;
className?: string;
}
interface MeteorData {
top: number;
left: number;
duration: number;
delay: number;
length: number;
}
function randomRange(min: number, max: number) {
return Math.random() * (max - min) + min;
}
function generateMeteors(
count: number,
minDuration: number,
maxDuration: number,
minLength: number,
maxLength: number
): MeteorData[] {
return Array.from({ length: count }, () => ({
top: randomRange(-10, 80),
left: randomRange(10, 110),
duration: randomRange(minDuration, maxDuration),
delay: randomRange(0, 10),
length: randomRange(minLength, maxLength),
}));
}
const meteorKeyframes = `
@keyframes meteorFall {
0% { opacity: 0; transform: rotate(215deg) translateX(0); }
5% { opacity: 1; }
70% { opacity: 1; }
100% { opacity: 0; transform: rotate(215deg) translateX(-120vh); }
}
`;
export function Meteors({
count = 20,
minDuration = 2,
maxDuration = 6,
minLength = 80,
maxLength = 200,
className = "",
}: MeteorsProps) {
const meteors = useMemo(
() => generateMeteors(count, minDuration, maxDuration, minLength, maxLength),
[count, minDuration, maxDuration, minLength, maxLength]
);
return (
<div
className={className}
style={{ position: "absolute", inset: 0, overflow: "hidden", pointerEvents: "none" }}
>
<style>{meteorKeyframes}</style>
{meteors.map((m, i) => (
<div
key={i}
style={{
position: "absolute",
top: `${m.top}%`,
left: `${m.left}%`,
width: m.length,
height: 1,
borderRadius: 999,
transform: "rotate(215deg)",
background:
"linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(148,163,184,0.3) 20%, rgba(255,255,255,0.8) 100%)",
animation: `meteorFall ${m.duration}s linear infinite`,
animationDelay: `${m.delay}s`,
opacity: 0,
}}
>
{/* Glowing head */}
<div
style={{
position: "absolute",
right: 0,
top: "50%",
transform: "translateY(-50%)",
width: 4,
height: 4,
borderRadius: "50%",
background: "#fff",
boxShadow: "0 0 6px 2px rgba(255,255,255,0.8), 0 0 12px 4px rgba(148,163,184,0.4)",
}}
/>
{/* Glow trail */}
<div
style={{
position: "absolute",
top: "50%",
right: 0,
transform: "translateY(-50%)",
width: "60%",
height: 3,
borderRadius: 999,
background:
"linear-gradient(90deg, transparent 0%, rgba(148,163,184,0.08) 40%, rgba(255,255,255,0.15) 100%)",
filter: "blur(1px)",
}}
/>
</div>
))}
</div>
);
}
// Demo usage
export default function MeteorsDemo() {
return (
<div
style={{
width: "100vw",
height: "100vh",
background: "linear-gradient(180deg, #0a0a0a 0%, #0f172a 50%, #0a0a0a 100%)",
display: "grid",
placeItems: "center",
position: "relative",
fontFamily: "system-ui, -apple-system, sans-serif",
overflow: "hidden",
}}
>
{/* Star field */}
<div
style={{
position: "absolute",
inset: 0,
backgroundImage: [
"radial-gradient(1px 1px at 10% 20%, rgba(255,255,255,0.4) 0%, transparent 100%)",
"radial-gradient(1px 1px at 30% 60%, rgba(255,255,255,0.3) 0%, transparent 100%)",
"radial-gradient(1px 1px at 50% 10%, rgba(255,255,255,0.5) 0%, transparent 100%)",
"radial-gradient(1px 1px at 70% 40%, rgba(255,255,255,0.2) 0%, transparent 100%)",
"radial-gradient(1px 1px at 90% 80%, rgba(255,255,255,0.4) 0%, transparent 100%)",
"radial-gradient(1px 1px at 15% 85%, rgba(255,255,255,0.3) 0%, transparent 100%)",
"radial-gradient(1px 1px at 45% 75%, rgba(255,255,255,0.2) 0%, transparent 100%)",
"radial-gradient(1px 1px at 65% 15%, rgba(255,255,255,0.5) 0%, transparent 100%)",
"radial-gradient(1px 1px at 85% 55%, rgba(255,255,255,0.3) 0%, transparent 100%)",
"radial-gradient(1px 1px at 25% 45%, rgba(255,255,255,0.4) 0%, transparent 100%)",
].join(","),
}}
/>
<Meteors count={20} />
<div style={{ position: "relative", zIndex: 10, textAlign: "center", pointerEvents: "none" }}>
<h1
style={{
fontSize: "clamp(2rem, 5vw, 3.5rem)",
fontWeight: 800,
letterSpacing: "-0.03em",
background: "linear-gradient(135deg, #e2e8f0 0%, #94a3b8 50%, #64748b 100%)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
backgroundClip: "text",
marginBottom: "0.5rem",
}}
>
Meteors
</h1>
<p
style={{
fontSize: "clamp(0.875rem, 2vw, 1.125rem)",
color: "rgba(148, 163, 184, 0.8)",
}}
>
Shooting stars streaking across the night sky
</p>
</div>
</div>
);
}<script setup>
function r(min, max) {
return Math.random() * (max - min) + min;
}
const meteors = Array.from({ length: 20 }, (_, i) => ({
id: i,
top: r(-10, 80),
left: r(10, 110),
duration: r(2, 6),
delay: r(0, 10),
length: r(80, 200),
}));
</script>
<template>
<div class="meteors-wrapper">
<div class="stars-bg"></div>
<div class="meteors-container">
<div
v-for="m in meteors"
:key="m.id"
class="meteor"
:style="{
top: m.top + '%',
left: m.left + '%',
width: m.length + 'px',
'--dur': m.duration + 's',
'--delay': m.delay + 's',
}"
></div>
</div>
<div class="meteors-content">
<h1 class="meteors-title">Meteors</h1>
<p class="meteors-subtitle">Shooting stars streaking across the night sky</p>
</div>
</div>
</template>
<style scoped>
.meteors-wrapper {
position: relative;
width: 100vw;
height: 100vh;
display: grid;
place-items: center;
background: linear-gradient(180deg, #0a0a0a 0%, #0f172a 50%, #0a0a0a 100%);
overflow: hidden;
font-family: system-ui, -apple-system, sans-serif;
}
.stars-bg {
position: absolute;
inset: 0;
background-image:
radial-gradient(1px 1px at 10% 20%, rgba(255,255,255,0.4) 0%, transparent 100%),
radial-gradient(1px 1px at 30% 60%, rgba(255,255,255,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 50% 10%, rgba(255,255,255,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 70% 40%, rgba(255,255,255,0.2) 0%, transparent 100%),
radial-gradient(1px 1px at 90% 80%, rgba(255,255,255,0.4) 0%, transparent 100%),
radial-gradient(1px 1px at 15% 85%, rgba(255,255,255,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 45% 75%, rgba(255,255,255,0.2) 0%, transparent 100%),
radial-gradient(1px 1px at 65% 15%, rgba(255,255,255,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 85% 55%, rgba(255,255,255,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 25% 45%, rgba(255,255,255,0.4) 0%, transparent 100%);
}
.meteors-container {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.meteor {
position: absolute;
transform: rotate(215deg);
height: 1px;
border-radius: 999px;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(148, 163, 184, 0.3) 20%,
rgba(255, 255, 255, 0.8) 100%
);
opacity: 0;
animation: meteorFall var(--dur, 3s) linear var(--delay, 0s) infinite;
}
.meteor::before {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
border-radius: 50%;
background: #fff;
box-shadow:
0 0 6px 2px rgba(255, 255, 255, 0.8),
0 0 12px 4px rgba(148, 163, 184, 0.4);
}
.meteor::after {
content: "";
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
width: 60%;
height: 3px;
border-radius: 999px;
background: linear-gradient(
90deg,
transparent 0%,
rgba(148, 163, 184, 0.08) 40%,
rgba(255, 255, 255, 0.15) 100%
);
filter: blur(1px);
}
@keyframes meteorFall {
0% {
opacity: 0;
transform: rotate(215deg) translateX(0);
}
5% {
opacity: 1;
}
70% {
opacity: 1;
}
100% {
opacity: 0;
transform: rotate(215deg) translateX(-120vh);
}
}
.meteors-content {
position: relative;
z-index: 10;
text-align: center;
pointer-events: none;
}
.meteors-title {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e2e8f0 0%, #94a3b8 50%, #64748b 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
}
.meteors-subtitle {
font-size: clamp(0.875rem, 2vw, 1.125rem);
color: rgba(148, 163, 184, 0.8);
font-weight: 400;
}
</style><script>
export let count = 20;
function r(min, max) {
return Math.random() * (max - min) + min;
}
const meteors = Array.from({ length: count }, (_, i) => ({
id: i,
top: r(-10, 80),
left: r(10, 110),
duration: r(2, 6),
delay: r(0, 10),
length: r(80, 200),
}));
</script>
<div class="meteors-wrapper">
<div class="stars-bg"></div>
<div class="meteors-container">
{#each meteors as m (m.id)}
<div
class="meteor"
style="
top: {m.top}%;
left: {m.left}%;
width: {m.length}px;
--dur: {m.duration}s;
--delay: {m.delay}s;
"
></div>
{/each}
</div>
<div class="meteors-content">
<h1 class="meteors-title">Meteors</h1>
<p class="meteors-subtitle">Shooting stars streaking across the night sky</p>
</div>
</div>
<style>
.meteors-wrapper {
position: relative;
width: 100vw;
height: 100vh;
display: grid;
place-items: center;
background: linear-gradient(180deg, #0a0a0a 0%, #0f172a 50%, #0a0a0a 100%);
overflow: hidden;
font-family: system-ui, -apple-system, sans-serif;
}
.stars-bg {
position: absolute;
inset: 0;
background-image:
radial-gradient(1px 1px at 10% 20%, rgba(255,255,255,0.4) 0%, transparent 100%),
radial-gradient(1px 1px at 30% 60%, rgba(255,255,255,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 50% 10%, rgba(255,255,255,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 70% 40%, rgba(255,255,255,0.2) 0%, transparent 100%),
radial-gradient(1px 1px at 90% 80%, rgba(255,255,255,0.4) 0%, transparent 100%),
radial-gradient(1px 1px at 15% 85%, rgba(255,255,255,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 45% 75%, rgba(255,255,255,0.2) 0%, transparent 100%),
radial-gradient(1px 1px at 65% 15%, rgba(255,255,255,0.5) 0%, transparent 100%),
radial-gradient(1px 1px at 85% 55%, rgba(255,255,255,0.3) 0%, transparent 100%),
radial-gradient(1px 1px at 25% 45%, rgba(255,255,255,0.4) 0%, transparent 100%);
}
.meteors-container {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.meteor {
position: absolute;
transform: rotate(215deg);
height: 1px;
border-radius: 999px;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(148, 163, 184, 0.3) 20%,
rgba(255, 255, 255, 0.8) 100%
);
opacity: 0;
animation: meteorFall var(--dur, 3s) linear var(--delay, 0s) infinite;
}
.meteor::before {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
border-radius: 50%;
background: #fff;
box-shadow:
0 0 6px 2px rgba(255, 255, 255, 0.8),
0 0 12px 4px rgba(148, 163, 184, 0.4);
}
.meteor::after {
content: "";
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
width: 60%;
height: 3px;
border-radius: 999px;
background: linear-gradient(
90deg,
transparent 0%,
rgba(148, 163, 184, 0.08) 40%,
rgba(255, 255, 255, 0.15) 100%
);
filter: blur(1px);
}
@keyframes meteorFall {
0% {
opacity: 0;
transform: rotate(215deg) translateX(0);
}
5% {
opacity: 1;
}
70% {
opacity: 1;
}
100% {
opacity: 0;
transform: rotate(215deg) translateX(-120vh);
}
}
.meteors-content {
position: relative;
z-index: 10;
text-align: center;
pointer-events: none;
}
.meteors-title {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e2e8f0 0%, #94a3b8 50%, #64748b 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
}
.meteors-subtitle {
font-size: clamp(0.875rem, 2vw, 1.125rem);
color: rgba(148, 163, 184, 0.8);
font-weight: 400;
}
</style>Meteors
Enchanting shooting stars that streak diagonally across the screen, each with a glowing trail that fades into the darkness. Perfect for space-themed or dramatic dark backgrounds.
How it works
- JavaScript generates meteor elements with randomized positions and delays
- Each meteor is a thin rotated line with a CSS gradient trail
@keyframesanimate position from top-right to bottom-left with opacity fade- Random animation durations and delays create natural, non-repetitive patterns
- Meteors loop infinitely with varying intervals
Customization
countprop controls meteor density- Change trail length via the
--meteor-lengthvariable - Adjust angle with
--meteor-angle(default 215deg diagonal) - Modify colors for warm, cool, or rainbow meteor showers
- Control speed range via JS constants
When to use it
- Space / astronomy themed pages
- Dark hero section backgrounds
- Night sky / dreamy aesthetics
- Loading screens or wait states