Rocket Starfield Pro
Rocket is currently in alpha – available with Datastar Pro.
A customizable starfield effect using HTML5 Canvas and reactive signals. Move your mouse over the starfield to control the center position.
Explanation #
Interactive starfield using Canvas and reactive signals. Mouse movement controls center position, sliders control speed and star count.
Usage Example #
Rocket Component #
1<template data-rocket:rocket-starfield
2 data-props:star-count="int|=500"
3 data-props:speed="float|clamp:1,100|=50"
4 data-props:center-x="float|clamp:0,100|=50"
5 data-props:center-y="float|clamp:0,100|=50">
6 <script>
7 const canvas = $$canvas;
8 if (!canvas) return console.error('Canvas not found');
9 canvas.removeAttribute('width');
10 canvas.removeAttribute('height');
11 const ctx = canvas.getContext('2d', { alpha: true, antialias: true, optimizeSpeed: true, willReadFrequently: false });
12 if (!ctx) return console.error('Canvas not supported');
13
14 const style = getComputedStyle(el);
15 const bg = style.backgroundColor, fg = style.color;
16 const strokeStyles = Array.from({ length: 10 }, (_, i) => `color-mix(in srgb, ${fg}, ${bg} ${i * 10}%)`);
17 let stars = [], t = 0, aid, ro;
18
19 const reset = () => {
20 const w = canvas.parentElement?.clientWidth || innerWidth;
21 const h = canvas.parentElement?.clientHeight || innerHeight;
22 canvas.width = w; canvas.height = h;
23 stars = Array.from({ length: $$starCount }, () => {
24 const x = Math.random() * w, y = Math.random() * h;
25 return { fx: x, fy: y, tx: x, ty: y, b: ~~(Math.random() * 100) };
26 });
27 };
28
29 const animate = (now = 0) => {
30 const cx = $$centerX, cy = $$centerY, speed = $$speed;
31 const { width: w, height: h } = canvas;
32 const centerX = w * cx / 100, centerY = h * cy / 100;
33 ctx.clearRect(0, 0, w, h);
34 ctx.fillStyle = bg;
35 ctx.fillRect(0, 0, w, h);
36 const v = ((1 + 8 * (speed - 1) / 99) * (t ? (now - t) / 1000 : 0)) * 0.5; t = now;
37 const buckets = {}, max = Math.min(w, h) * 0.1;
38 for (const s of stars) {
39 s.fx = s.tx; s.fy = s.ty;
40 const dx = (s.tx - centerX) * v, dy = (s.ty - centerY) * v;
41 s.tx += Math.max(-max, Math.min(max, dx)); s.ty += Math.max(-max, Math.min(max, dy));
42 s.b = Math.min(100, s.b + 1);
43 if (s.fx < 0 || s.fx > w || s.fy < 0 || s.fy > h) {
44 s.tx = s.fx = Math.random() * w;
45 s.ty = s.fy = Math.random() * h;
46 s.b = ~~(Math.random() * 100);
47 continue;
48 }
49 const bucket = ~~(s.b / 10);
50 if (!buckets[bucket]) buckets[bucket] = new Path2D();
51 buckets[bucket].moveTo(s.fx, s.fy); buckets[bucket].lineTo(s.tx, s.ty);
52 }
53 ctx.lineWidth = 1;
54 for (const b in buckets) { ctx.strokeStyle = strokeStyles[b]; ctx.stroke(buckets[b]); }
55 aid = requestAnimationFrame(now => animate(now));
56 };
57
58 reset(); animate();
59 ro = new ResizeObserver(reset); ro.observe(canvas.parentElement || document.body);
60 let count = $$starCount;
61 const cleanup = effect(() => {
62 const c = $$starCount;
63 if (c !== count) { count = c; reset(); }
64 });
65
66 // Cleanup on disconnect
67 onCleanup(() => {
68 cancelAnimationFrame(aid);
69 ro.disconnect();
70 cleanup();
71 });
72 </script>
73 <style>
74 .rocket-starfield {
75 display: block;
76 width: 100%;
77 height: 100%;
78
79 canvas {
80 display: block;
81 width: 100%;
82 height: 100%;
83 }
84 }
85 </style>
86 <div class="rocket-starfield">
87 <canvas data-ref="canvas" />
88 </div>
89</template>