Nebula

A drifting WebGL nebula in dithered pixels. Speed, density and palette are attributes.

mbolli <sb-nebula> webgl shader background space animation Since 2026-09-22 3.6 kB1Open in playground Edit on GitHub

Playground

Clouds of gas and a few twinkling stars, rendered by a fragment shader. The shader draws domain-warped noise at low resolution and posterizes it with an ordered dither, so it looks like pixel art. Colours come from theme tokens, and the nebula leans toward the pointer.

Examples

Controls

<div data-signals="{_speed: 1, _density: 0.5, _pixel: 4}" style="display: grid; gap: 16px; inline-size: 100%">
  <sb-nebula data-attr:speed="$_speed" data-attr:density="$_density" data-attr:pixel="$_pixel" data-preserve-attr="speed density pixel"></sb-nebula>
  <div style="display: flex; gap: 24px; flex-wrap: wrap">
    <sb-slider label="Speed" max="5" step="0.1" style="flex: 1; min-inline-size: 10rem" data-bind:_speed__prop.value></sb-slider>
    <sb-slider label="Density" max="1" step="0.05" style="flex: 1; min-inline-size: 10rem" data-bind:_density__prop.value></sb-slider>
    <sb-slider label="Pixel size" min="1" max="16" style="flex: 1; min-inline-size: 10rem" data-bind:_pixel__prop.value></sb-slider>
  </div>
</div>

Palettes

<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: 12px; inline-size: 100%">
  <sb-nebula palette="violet" style="--sb-nebula-height: 7rem"></sb-nebula>
  <sb-nebula palette="aurora" seed="7" style="--sb-nebula-height: 7rem"></sb-nebula>
  <sb-nebula palette="ember" seed="3" density="0.7" style="--sb-nebula-height: 7rem"></sb-nebula>
  <sb-nebula palette="mono" seed="12" style="--sb-nebula-height: 7rem"></sb-nebula>
</div>

Smooth

levels="0" turns off the posterizing, and pixel="1" renders at full resolution.

<sb-nebula levels="0" pixel="2" speed="0.6" style="--sb-nebula-height: 9rem"></sb-nebula>

Performance

One full-screen triangle and a single shader, rendered at a quarter of the CSS resolution by default (pixel="4"). It stops animating offscreen and when speed is 0. It frees its WebGL context when removed, and recovers when the browser takes the context away. Without WebGL it shows a still gradient in the same colours. Set the height with --sb-nebula-height.

Accessibility

It is decorative motion, labelled "Animated nebula" (change it with label). Under prefers-reduced-motion it shows one still frame and ignores the pointer.

Installation

Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.

<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
     It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
  { "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>

<sb-nebula speed="1.2" density="0.6" style="--sb-nebula-height: 7.5rem"></sb-nebula>

<!-- In production, pin today's catalog instead of the latest: the browser then
     refuses any file that changed. Add "integrity" to the import map above: the
     hashes from https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/importmap.json and Datastar's, below. -->
<!--
<script type="importmap">
  { "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" },
    "integrity": { "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js": "sha384-vUxZojLrF1Ar3de5h7VINqhJXBgjyZS4U49pHvGa87kum6j5Xn6JRziuARNw5ELG", "…": "…from importmap.json" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/autoloader.js" integrity="sha384-IvSCUjEWPS+wwXunU+48x96KmyWDx/Y4LeCwCL8cWFqJ/zHmTsTOsSMrzTOcpW3D"></script>
-->

<!-- Or load just this component, pinned to this version: -->
<!-- <script type="module" src="https://starbase.zweiundeins.gmbh/c/nebula@086f49506cc1/nebula.js" integrity="sha384-Mbx6W3bCWGzD8bSkpkHDFadxZRvclHB//ZQWfjQmtU+iwJy/PTBtFd6cw08FOFTZ"></script> -->

Size

Each file compressed on its own, the way it is served (gzip -9, brotli -11). Datastar and Rocket are shared by every component and not counted.

FileOriginalgzipbrotli
nebula.js 9.6 kB4.1 kB3.6 kB

API reference

Props

AttributeTypeDefaultDescription
speednumber1Drift speed (0 is still).
densitynumber0.5How much of the sky the gas covers, 0 to 1.
palette"violet" | "aurora" | "ember" | "mono""violet"Colour scheme (from theme tokens).
pixelnumber4Size of one pixel, in CSS pixels.
levelsnumber6Colour levels per channel, dithered (0 for smooth).
starsbooleantrueDraw twinkling stars.
seednumber0Picks a different nebula.
parallaxbooleantrueShift with the pointer.
labelstring"Animated nebula"Accessible label.