Voxel

A tiny 3D voxel renderer you steer with signals. No WebGL.

zweiundeins <sb-voxel> 3d canvas animation pixel model Since 2026-09-21 4.4 kB0Open in playground Edit on GitHub

Playground

A software 3D renderer for voxel models: a rocket, a satellite and a ringed planet. It rasterises into a 128×128 buffer with a z-buffer, uses flat, stepped shading and a pixel outline, and is scaled up with crisp edges. Drag it or use the arrow keys to orbit. Every angle is an attribute, so Datastar signals can steer it.

Examples

Steered by sliders

Each slider is bound to a local signal, and each signal drives an attribute with data-attr. data-preserve-attr keeps those attributes when a server frame morphs the page.

<div data-signals="{_yaw: 30, _pitch: 15, _zoom: 1, _light: 45}" style="display: flex; flex-wrap: wrap; gap: 24px; align-items: center; inline-size: 100%">
  <sb-voxel
    data-attr:yaw="$_yaw" data-attr:pitch="$_pitch" data-attr:zoom="$_zoom" data-attr:light="$_light"
    data-preserve-attr="yaw pitch zoom light"></sb-voxel>
  <div style="display: grid; gap: 16px; flex: 1; min-inline-size: 14rem">
    <sb-slider label="Yaw" min="-180" max="180" unit="°" data-bind:_yaw__prop.value></sb-slider>
    <sb-slider label="Pitch" min="-89" max="89" unit="°" data-bind:_pitch__prop.value></sb-slider>
    <sb-slider label="Zoom" min="0.25" max="2.5" step="0.05" data-bind:_zoom__prop.value></sb-slider>
    <sb-slider label="Light" min="0" max="360" step="5" unit="°" data-bind:_light__prop.value></sb-slider>
  </div>
</div>

Models

<sb-voxel model="rocket" spin="30" style="--sb-voxel-size: 10rem"></sb-voxel>
<sb-voxel model="satellite" spin="-20" pitch="25" style="--sb-voxel-size: 10rem"></sb-voxel>
<sb-voxel model="planet" spin="15" pitch="20" style="--sb-voxel-size: 10rem"></sb-voxel>

Report the orbit

After a drag, sb-orbit reports the effective angles.

<div data-signals:_orbit="'drag the satellite'">
  <sb-voxel model="satellite" data-on:sb-orbit="$_orbit = 'yaw ' + Math.round(evt.detail.yaw) + '°, pitch ' + Math.round(evt.detail.pitch) + '°'"></sb-voxel>
  <p data-text="$_orbit"></p>
</div>

Performance

It only paints when something changes: a prop, a drag, or each frame while spin is set. Offscreen instances pause, and with prefers-reduced-motion the model doesn't spin.

Accessibility

The canvas has role="img", and its label always states the model and current angles. It is focusable, and the arrow keys orbit (Shift for bigger steps).

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-voxel spin="40" pitch="18" style="--sb-voxel-size: 8.5rem"></sb-voxel>

<!-- 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/voxel@0919f1f8658e/voxel.js" integrity="sha384-wrEanlfgKgo3QI4mIlwiG+JlZBEs/FntmNVDf64c9hz6jWP0wKhWPYIEYns1cJtL"></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
voxel.js 12.3 kB5.0 kB4.4 kB

API reference

Props

AttributeTypeDefaultDescription
model"rocket" | "satellite" | "planet""rocket"Which voxel model to show.
yawnumber30Rotation around the vertical axis, in degrees.
pitchnumber15Tilt toward the viewer, in degrees.
zoomnumber1Scale factor.
spinnumber0Auto-rotation speed, in degrees per second.
lightnumber45Light direction around the model, in degrees.

Events

NameDescription
sb-orbitAfter a drag. detail: { yaw, pitch } (effective angles).