UI 컴포넌트 Easy
Meteors
Animated shooting stars and meteors falling diagonally across the screen with glowing trails, random positions, and staggered animation delays.
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