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.

Demo
x: , y:

Explanation #

Interactive starfield using Canvas and reactive signals. Mouse movement controls center position, sliders control speed and star count.

Usage Example #

1<rocket-starfield
2    data-attr:center-x="$centerX"
3    data-attr:center-y="$centerY"
4    data-attr:speed="$speed"
5    data-attr:star-count="$starCount"
6></rocket-starfield>

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>