@gomani/scene
v0.9.0
Published
Heavy media that degrades instead of breaking: a network-aware <Scene> with progressive LOD and fidelity fallback.
Maintainers
Readme
@gomani/scene
Heavy media that degrades instead of breaking (P7). Referencing a 3D scene yields the right thing automatically: a pre-rendered poster always ships (zero WebGL), the live scene is an island loaded only on capable connections, and even then it streams fidelity up from a coarse level of detail within a byte budget — never a single blocking megabyte-scale download.
The <Scene> primitive (SSR, network-aware)
import { Scene } from '@gomani/scene';
import MaskViewer from '../islands/mask.js'; // an app island that mounts the renderer
<Scene
poster="/mask-poster.svg"
alt="A carved mask, rotatable in 3D"
width={640}
height={480}
full={<MaskViewer />} // emitted ONLY on the capable tier
/>;On the lean tier (via @gomani/adaptive) it renders only the poster (or a short pre-rendered video);
the full island — and every WebGL/decoder/LOD byte behind it — never reaches the page. It holds the
exact aspect ratio (no layout shift) and is accessible (role="img" + alt).
The client (@gomani/scene/client)
mountScene(canvas, { manifest, tier }) runs the progressive loader — fetching the planned levels
low → high so a coarse scene appears fast and refines — and hands each level to a pluggable
SceneRenderer. The built-in canvasRenderer is dependency-free (a rotating point cloud); a
production three.js / Draco / KTX2 adapter implements the same interface, with its heavy deps
confined to the scene island on the capable tier.
const manifest = {
poster: '/mask-poster.svg',
width: 640,
height: 480,
levels: [
{ level: 0, url: '/lod0.json', bytes: 1_437, kind: 'points' },
{ level: 1, url: '/lod1.json', bytes: 5_475, kind: 'points' },
{ level: 2, url: '/lod2.json', bytes: 12_610, kind: 'points' },
],
};
mountScene(canvas, { manifest, tier: networkQuality.value.tier });The fidelity policy (chooseFidelity / pickLevels) is pure and exported: lean → poster/video;
capable → the levels that fit maxBytes (level 0 is always included — something usable always renders).
supportsOffscreenCanvas() gates worker offload for a worker-capable renderer.
See examples/scene for a complete demo. Real WebGL + throttled-profile measurement lands in the
bench.
