Sternschnuppen
Feine Streifen, die schräg über die Bühne ziehen, aus festen Startpunkten über leisen Sternen.
Eine von 6 Components in Beams, Partikel und Bewegung
Details
Was Sternschnuppen mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- Beams, Partikel und Bewegung
- Bereich
- Elements
- Variants
- 3
- Version
- 1.0.0 · seit 1.0.0
- Datei
- components/ui/meteors.tsx
- Bringt mit
- utilskit
- npm-Pakete
- Keine
- Sprachen
- Texte kommen über Props
- Studio
- Im Code anpassen
- Mit KI bauen
- Per MCP in Claude, Cursor, Copilot und Codex
Props
Die Schnittstelle MeteorsProps aus components/ui/meteors.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.
countoptionalnumberNumber of streaks, default 12
angleoptionalnumberDirection of travel in degrees on screen, 0 is to the right, 90 straight down. Default 135 (down to the left).
speedoptionalnumberAverage seconds between two passes of one streak, default 10
lengthoptionalnumberTail length in px, default 140
toneoptional'accent' | 'neutral'accent tints the head, neutral stays in the text color
starsoptionalnumberFaint fixed stars behind the streaks, default 40
seedoptionalnumberSame seed, same sky
parallaxoptionalnumberPixels the field of stars drifts against the page over a full scroll pass, like a far sky. Default 0 (off), 40 reads as depth.
Barrierefreiheit
Aus dem Quellcode der Component abgeleitet.
- Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.
Bewegung
- Bewegt sich mit dem Scrollen, der Fortschritt folgt der Position auf der Seite.
- Bringt eigene Animationen mit, ruhig getaktet und ohne Wackeln.
- Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig.
Quellcode
Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.
Datei anzeigen, 158 Zeilen
'use client';
import { useEffect, useRef } from 'react';
import { cn, prefersReducedMotion, round } from '@/lib/kit';
export interface MeteorsProps {
/** Number of streaks, default 12 */
count?: number;
/** Direction of travel in degrees on screen, 0 is to the right, 90 straight down. Default 135 (down to the left). */
angle?: number;
/** Average seconds between two passes of one streak, default 10 */
speed?: number;
/** Tail length in px, default 140 */
length?: number;
/** accent tints the head, neutral stays in the text color */
tone?: 'accent' | 'neutral';
/** Faint fixed stars behind the streaks, default 40 */
stars?: number;
/** Same seed, same sky */
seed?: number;
/** Pixels the field of stars drifts against the page over a full scroll pass, like a far sky. Default 0 (off), 40 reads as depth. */
parallax?: number;
className?: string;
}
const CSS = `
@keyframes kitfx-meteor {
0% { transform: translate3d(0, 0, 0); opacity: 0; }
4% { opacity: 1; }
32% { opacity: 1; }
40%, 100% { transform: translate3d(var(--kitfx-d), 0, 0); opacity: 0; }
}
.kitfx-meteors * { animation-play-state: var(--kitfx-play, running); }
@media (prefers-reduced-motion: reduce) {
.kitfx-meteors-sky { transform: none !important; }
.kitfx-meteors [data-meteor] { animation: none !important; transform: translate3d(var(--kitfx-still), 0, 0); opacity: var(--kitfx-still-o); }
}
`;
function seeded(seed: number) {
let a = seed >>> 0 || 1;
return () => {
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
/**
* Scroll pass. While `on`, writes --kitfx-p on the element: 0 when its top
* meets the bottom of the window, 1 when its bottom leaves at the top. Never
* with reduced motion, the CSS then falls back to the resting value.
*/
function useScrollPass(ref: { current: HTMLElement | null }, on: boolean) {
useEffect(() => {
const el = ref.current;
if (!el || !on || prefersReducedMotion()) return;
let frame = 0;
const update = () => {
frame = 0;
const r = el.getBoundingClientRect();
const p = Math.min(1, Math.max(0, (window.innerHeight - r.top) / (r.height + window.innerHeight)));
el.style.setProperty('--kitfx-p', p.toFixed(4));
};
const onScroll = () => {
if (!frame) frame = requestAnimationFrame(update);
};
update();
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll);
return () => {
cancelAnimationFrame(frame);
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
el.style.removeProperty('--kitfx-p');
};
}, [ref, on]);
}
/**
* Thin streaks that cross the stage on a slant, from seeded positions so
* server and browser agree. Fills its parent, decorative. With reduced
* motion a few streaks stand still mid flight, the rest stay hidden.
*/
export function Meteors({ count = 12, angle = 135, speed = 10, length = 140, tone = 'accent', stars = 40, seed = 5, parallax = 0, className }: MeteorsProps) {
const ref = useRef<HTMLDivElement>(null);
useScrollPass(ref, parallax !== 0);
useEffect(() => {
const el = ref.current;
if (!el || typeof IntersectionObserver === 'undefined') return;
const io = new IntersectionObserver(([e]) => el.style.setProperty('--kitfx-play', e.isIntersecting ? 'running' : 'paused'));
io.observe(el);
return () => io.disconnect();
}, []);
const rnd = seeded(seed);
const rad = (angle * Math.PI) / 180;
// Start on the edges the streaks come from, spread so they cross the whole stage
const fromRight = Math.cos(rad) < 0;
const meteors = Array.from({ length: count }, (_, i) => {
const along = rnd();
const onTop = rnd() < 0.72;
const x = onTop ? round(-10 + along * 120) : fromRight ? 104 : -4;
const y = onTop ? -4 : round(along * 70);
const dur = round(speed * (0.7 + rnd() * 0.7), 1);
return {
x,
y,
dur,
delay: round(-rnd() * dur, 1),
dist: Math.round(700 + rnd() * 600),
len: Math.round(length * (0.6 + rnd() * 0.7)),
show: i % 3 === 0,
still: round(0.12 + rnd() * 0.2),
};
});
const sky = Array.from({ length: stars }, () => ({ x: round(rnd() * 100), y: round(rnd() * 100), s: rnd() > 0.85 ? 2 : 1, o: round(0.25 + rnd() * 0.6) }));
const head = tone === 'accent' ? 'color-mix(in oklab, var(--primary) 80%, var(--foreground))' : 'var(--foreground)';
return (
<div ref={ref} aria-hidden data-clip-ok className={cn('kitfx-meteors pointer-events-none absolute inset-0 overflow-hidden', className)}>
<style dangerouslySetInnerHTML={{ __html: CSS }} />
<div className="kitfx-meteors-sky absolute inset-0" style={parallax ? { transform: `translate3d(0, calc((var(--kitfx-p, 0.5) - 0.5) * ${parallax}px), 0)` } : undefined}>
{sky.map((s, i) => (
<span
key={`s${i}`}
className="absolute rounded-full bg-foreground"
style={{ left: `${s.x}%`, top: `${s.y}%`, width: s.s, height: s.s, opacity: round(s.o * 0.3) }}
/>
))}
</div>
{meteors.map((m, i) => (
<div key={i} className="absolute" style={{ left: `${m.x}%`, top: `${m.y}%`, transform: `rotate(${angle}deg)`, transformOrigin: '0 0' }}>
<div
data-meteor=""
className="relative h-px opacity-0"
style={{
width: m.len,
marginLeft: -m.len,
['--kitfx-d' as string]: `${m.dist}px`,
['--kitfx-still' as string]: `${Math.round(m.dist * m.still)}px`,
['--kitfx-still-o' as string]: m.show ? 0.8 : 0,
animation: `kitfx-meteor ${m.dur}s linear ${m.delay}s infinite`,
}}
>
{/* tail fades out behind the head, the head carries a small glow */}
<span className="absolute inset-0 rounded-full opacity-60 dark:opacity-80" style={{ background: `linear-gradient(to right, transparent, color-mix(in oklch, ${head} 70%, transparent))` }} />
<span
className="absolute right-0 top-1/2 size-[3px] -translate-y-1/2 rounded-full"
style={{ background: head, boxShadow: `0 0 0 1px color-mix(in oklch, ${head} 18%, transparent), 0 0 10px 2px color-mix(in oklch, ${head} 40%, transparent)` }}
/>
</div>
</div>
))}
</div>
);
}Häufige Fragen
Wie installiere ich Sternschnuppen?
Mit aiw add @ai-website/meteors. Der Befehl legt die Datei components/ui/meteors.tsx in dein Projekt, der Code gehört danach dir. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Sternschnuppen?
3 Varianten, Schauer, Ruhiger Himmel und Steil.
Kann ein KI-Assistent Sternschnuppen einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Sternschnuppen mit aiw_components und installieren sie mit aiw_add @ai-website/meteors.