npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hozo/three

v0.2.0

Published

Projects Three.js scenes into portable Hozo Canvas scenes.

Downloads

296

Readme

@hozo/three

Projects a documented subset of an ordinary Three.js scene into a portable @hozo/canvas scene. It is useful for flat 3D diagrams, data visualisations, and a deterministic fallback where a GPU renderer is unavailable.

import { projectThreeScene } from '@hozo/three'
import { BoxGeometry, Mesh, MeshBasicMaterial, PerspectiveCamera, Scene } from 'three'

const scene = new Scene()
scene.add(new Mesh(new BoxGeometry(), new MeshBasicMaterial({ color: '#2563eb' })))

const camera = new PerspectiveCamera(50, 16 / 9, 0.1, 100)
camera.position.z = 4

const projected = projectThreeScene(scene, camera, { width: 320, height: 180 })

For React, the same boundary is available as a universal Canvas component:

import { ThreeCanvas } from '@hozo/three'

<ThreeCanvas
  decorative
  scene={scene}
  camera={camera}
  width={320}
  height={180}
/>

width and height are optional. When either is omitted, the Web renderer families follow a ResizeObserver, while the portable surface uses the same measurement on Web and onLayout on Native. The server-rendered fallback is 300 by 150, and onResize reports the resolved layout size and pixel ratio. Give the surface a responsive CSS or Native layout size, for example style={{ width: '100%', height: '100%' }}; without one it keeps the stable 300-by-150 fallback.

Perspective cameras follow the resolved aspect ratio by default. Use cameraResize="manual" when the application owns its projection. Orthographic cameras are always application-owned because resizing their frustum has no single correct policy.

Rendering is demand-driven by default. Change the revision prop or call invalidate() through a ref after imperative scene mutations. Animated scenes can opt into frameloop="always" and mutate their Three objects in onFrame. GPU surfaces render those continuous frames directly rather than scheduling a React update. If an onFrame mutation changes semantic structure such as an object's name, visibility, or layer, also change revision when that semantic change occurs so its keyboard and screen-reader controls are rebuilt.

Classic WebGL renderer

Web applications that need Three.js lighting, shader materials, depth, or the established WebGLRenderer ecosystem can select the classic renderer family explicitly:

import { ThreeCanvas } from '@hozo/three/webgl'

<ThreeCanvas
  accessibilityLabel="Interactive product model"
  scene={scene}
  camera={camera}
  width={640}
  height={360}
/>

This entry point owns renderer creation, sizing, drawing, and disposal while sharing the portable surface's demand/continuous frame-loop contract. It is currently Web-only; importing it on React Native fails explicitly because Hozo does not yet provide a Native WebGL context host. It never silently falls back to the portable renderer. The modern WebGPURenderer family uses a separate @hozo/three/webgpu entry point.

onObjectPress raycasts the classic WebGL scene and preserves the source Object3D and its intersections. Named interactive meshes, lines, points, and sprites also receive real off-screen buttons, so the same object can be reached with a pointer, keyboard, or screen reader. getAccessibilityLabel can supply those names without mutating object.name.

Modern WebGPU renderer family

New Web applications can select Three.js's modern renderer family explicitly:

import { ThreeCanvas } from '@hozo/three/webgpu'

<ThreeCanvas
  accessibilityLabel="Interactive product model"
  scene={scene}
  camera={camera}
  width={640}
  height={360}
/>

Hozo loads three/webgpu lazily and awaits WebGPURenderer.init() before the first draw. Three.js chooses WebGPU when available and otherwise uses its own modern WebGL 2 backend; pass rendererOptions={{ forceWebGL: true }} to force that backend for testing. This is distinct from the classic @hozo/three/webgl renderer and shares its DOM interaction and accessibility contract rather than its material or post-processing extension points.

Pass onObjectPress to receive the projected Three object and its raycast intersections. Set object.name, or provide getAccessibilityLabel, to expose one keyboard and screen-reader control per object even when it projects into many triangles.

onObjectActiveChange reports the same object-level state for mouse or pen hover, keyboard focus, and touch hold. Moving across triangle boundaries inside one object does not emit a false leave and re-entry.

React Three Fiber authoring

Applications that author scenes with React Three Fiber can opt into a separate entry point. R3F continues to own its reconciler, renderer, frame loop, and pointer-event system; Hozo supplies the responsive container and the same label, fallback, or decorative accessibility choice as its direct surfaces.

import { ThreeCanvas } from '@hozo/three/r3f'

<ThreeCanvas
  accessibilityLabel="Rotating product preview"
  style={{ width: '100%', height: 360 }}
  camera={{ position: [0, 2, 5] }}
>
  <ambientLight intensity={0.5} />
  <mesh>
    <boxGeometry />
    <meshStandardMaterial color="orange" />
  </mesh>
</ThreeCanvas>

@react-three/fiber is an optional peer and is retained only by this entry point. Object-level keyboard activation uses an explicit list rather than R3F's private instance metadata:

const product = useRef<THREE.Mesh>(null)

<ThreeCanvas
  accessibilityLabel="Product preview"
  accessibleObjects={[
    {
      id: 'product',
      label: 'Inspect product',
      object: product,
      onPress: () => openInspector(),
    },
  ]}
>
  <mesh ref={product} onClick={() => openInspector()}>
    <boxGeometry />
    <meshStandardMaterial color="orange" />
  </mesh>
</ThreeCanvas>

The native control handles keyboard and screen-reader activation. The mesh keeps its ordinary R3F pointer handler, so Hozo does not synthesize or double-dispatch pointer events. A registered object with href becomes a real anchor instead, preserving modifier-click and application-router integration.

Hidden until focus reaches it. The controls sit off-screen while nothing is focused — a canvas should not carry a row of buttons on every scene — and the strip is revealed as soon as focus enters it, in the user agent's own Canvas/CanvasText colours. It was clipped in both states until #689, which meant a sighted keyboard user tabbed onto a control they could not see anywhere: measured at 1×1 with clip: rect(0, 0, 0, 0) around a 136×24 button that had Chrome's focus ring on it, clipped away with everything else. WCAG 2.4.7, and invisible to every other check here — axe reads the accessibility tree, where the control was correct.

controlClassName dresses each control for applications that want their own look; the package's two colours are the two a user agent guarantees contrast between, and nothing more.

R3F with WebGPU

R3F also accepts an asynchronous renderer factory. Keep the factory stable and initialize Three's modern renderer before returning it:

import type { R3FRendererFactoryProps } from '@hozo/three/r3f'

const createWebGPURenderer = async ({ canvas }: R3FRendererFactoryProps) => {
  const { WebGPURenderer } = await import('three/webgpu')
  const renderer = new WebGPURenderer({
    canvas: canvas as HTMLCanvasElement,
    antialias: true,
  })
  await renderer.init()
  return renderer
}

<ThreeCanvas accessibilityLabel="Product preview" gl={createWebGPURenderer}>
  {/* ordinary R3F scene */}
</ThreeCanvas>

The dynamic import keeps Three's modern renderer out of applications that use the default R3F WebGL path. Hozo passes the factory through; R3F owns awaiting, configuration, frame scheduling, error propagation, and disposal. Runtime GPU availability still belongs to the browser and should be covered by an application-level fallback or error boundary.

Experimental R3F Native host

Expo prebuild applications can opt into the Native GPU host explicitly:

import { ThreeCanvas } from '@hozo/three/r3f-native'

<ThreeCanvas
  accessibilityLabel="Rotating product preview"
  style={{ flex: 1 }}
  camera={{ position: [0, 2, 5] }}
>
  <ambientLight intensity={0.5} />
  <mesh>
    <boxGeometry />
    <meshStandardMaterial color="orange" />
  </mesh>
</ThreeCanvas>

Install @react-three/fiber and three, then use npx expo install expo-gl expo-asset expo-file-system so the GL host matches the Expo SDK. This entry is experimental and separate from @hozo/three/r3f: R3F Native owns its Expo GL context, frame loop, and touch raycasting, while Hozo supplies the same label/fallback/decorative choice and explicit semantic object controls.

Keep one Three instance in Metro: R3F Native patches the CJS TextureLoader for Expo image uploads, while ESM imports can otherwise select a second Three build with a DOM-only image loader. Resolve bare three requests to require.resolve('three') for both application and addon imports, as in the probe's Metro configuration.

The measured support claim is Expo prebuild on Android and iOS. Android has release evidence for touch, TalkBack, Activity resume, frame sampling, and teardown. The iOS Simulator has release evidence for initialization, background/resume, frame sampling, and teardown; trusted pointer and VoiceOver operation still need device evidence. Expo Go, bare React Native setup, and forced driver/OS context loss are not yet claimed.

Portable projection details

The result is a retained CanvasScene, with world transforms and camera projection already baked into its paths. Indexed and non-indexed triangle BufferGeometry, perspective and orthographic cameras, clipping, face sides, and flat MeshBasicMaterial colours are supported. Normal alpha transparency maps to Canvas opacity and paints after opaque primitives. ArrayCamera projects each child camera into its declared bottom-left viewport. Line, LineSegments, and LineLoop with LineBasicMaterial are projected with their declared colour and width. Solid and dashed lines interpolate RGB vertex colours through portable Canvas gradients, including clipped endpoints. Opaque RGB lines and MeshBasicMaterial wireframe edges approximate linear-RGB and perspective-correct interpolation with adaptive encoded colour stops. Each edge still emits one primitive, capped at 32 stops; short projected edges have smaller budgets. Set lineColorInterpolation="endpoints" on ThreeCanvas, or pass the same option to projectThreeScene, to keep the cheaper two-stop path for large animated scenes. Uniform colours use two stops. Transparency, fog, normal-derived gradients and non-default Three colour-management configurations retain their existing approximation. The cache holds at most 256 gradient conditions; many unique animated depths can miss it on every frame. Solid meshes likewise preserve RGB vertex attributes and interpolate colours across clipped triangles. RGBA attributes also preserve per-vertex alpha when the material opts into transparency; opaque RGBA and alpha-tested gradients remain diagnostic because Canvas source-over would change WebGL blend semantics. The portable mesh interpolation is screen-space; perspective-correct interpolation remains a GPU-backend concern. Primary MeshBasicMaterial.map colour textures also project when they use SRGBColorSpace, clamp, repeat, or mirrored wrapping on either axis, a URL/URI or Native asset source, and complete two-component UVs. Texture transforms and flipY are preserved, including UVs created by clipping. Sampling is affine in screen space and has no mipmaps. Mesh, point, and sprite material colours multiply the map's linear RGB while preserving its alpha. Web caches up to eight processed colours per decoded image (fewer for large images); non-white tints require same-origin or CORS-enabled images, and continuously animated tints should use a GPU renderer instead. Native composes Skia's decode, colour matrix, and encode filters. Vertex-colour/fog modulation, and per-pixel alpha tests stay diagnostic instead of being silently approximated. Solid Scene.background colours and constrained 2D colour textures fill the portable viewport without creating an interactive object. Cube/equirectangular environment backgrounds remain diagnostic. Ordinary 2D texture backgrounds support linear-RGB intensity; backgroundBlurriness is a no-op, matching Three.js. Fog and FogExp2 blend supported meshes, wireframes, lines, points, and sprites in the same working colour space as Three.js, while respecting each material's fog opt-out. Constant-depth shapes and individual points match the shader formula; varying-depth portable gradients evaluate fog at their vertices or endpoints rather than per fragment. Uniform alphaTest is preserved for the untextured subset by omitting a whole primitive when its material opacity is below the threshold. Per-vertex alpha, alpha hashing, MSAA alpha-to-coverage, and dithering are diagnosed rather than silently flattened into ordinary opacity. LineDashedMaterial uses Three's lineDistance, dash size, gap size, and scale to emit portable segments before projection. PointsMaterial projects untextured points as circles and the constrained colour-map subset as screen-aligned point-sprite quads, including perspective size attenuation. Per-point RGB colours remain available for the untextured path. Wireframe MeshBasicMaterial is projected through the same line pipeline, including uniform or per-vertex RGB colour and wireframeLinewidth. Default world-space material clipping planes cut meshes and lines and discard clipped points. clipIntersection preserves the union of material half-spaces for meshes, lines, points, and sprites. Mesh material arrays and BufferGeometry groups preserve each group's material and intersect with the geometry's draw range just as they do in Three.js. Scene.overrideMaterial replaces supported render-list materials while preserving source visibility and allowOverride. LOD selects its active level from the camera distance. SpriteMaterial billboards preserve their centre, rotation, scale, and size attenuation for solid colours and the same constrained affine colour-map subset as meshes. InstancedMesh applies each instance transform, colour, and morph weight. BatchedMesh projects its geometry ranges, sparse instance IDs, visibility, transforms, and optional instance colours through public Three.js APIs. Position morph targets are evaluated for meshes, points, and lines, including relative morph geometry. SkinnedMesh uses Three's public CPU vertex evaluation so morphs, bind matrices, bone weights, and the current skeleton pose remain aligned.

This is deliberately not a software WebGL implementation. General GPU texture sampling, lighting, custom blending, shaders, post-processing, and XR need a GPU backend. Unsupported inputs are omitted and returned as diagnostics rather than rendered misleadingly.

MeshNormalMaterial is the first non-basic portable mesh material: smooth and flat view-space normals become Canvas vertex colours for ordinary and instanced meshes, while wireframes become normal-coloured Canvas gradients. Each instance uses its own normal matrix, and ordinary meshes blend absolute or relative position and normal morph targets before projection. Skinned meshes apply those morphs before the weighted bone and bind-matrix transforms used by Three.js. Texture-perturbed normals and instanced morph textures remain diagnostic rather than receiving an inaccurate approximation.

The version-pinned Three.js coverage report separates atomic portable capabilities from class-level Three.js surface classification; both link every claim back to executable tests. Every current portable surface category now has an independently owned capability inventory, so the report can publish an overall portable ceiling without presenting it as real-scene success. The separate real-scene report runs six version-pinned archetypes and publishes the same observations as machine-readable JSON. Portable results are measured deterministically; environment-dependent renderer families remain explicitly not-run in that checked-in report rather than being inferred from their API surface.

GPU renderer conformance is environment-dependent and is therefore measured on main, weekly, and on demand rather than folded into the portable percentage. Run pnpm --filter @hozo/three test:gpu to produce JSON and Markdown reports for classic WebGL, the modern renderer's automatic backend choice, and its forced WebGL 2 backend. A missing native WebGPU backend is recorded as unavailable; classic WebGL and modern forced-WebGL 2 remain required to draw and expose working semantic controls. Every available renderer mode also executes the same six real-scene fixtures and requires useful draws, semantic controls, and activation for every scene. This makes forced WebGL 2 the stable Modern-family baseline while native WebGPU remains separately observable.

The product-viewer glTF fixture also decodes an embedded 4x4 PNG through the browser's TextureLoader and advances its AnimationMixer using the surface's onFrame callback. The browser probe waits for real frames, records the rotation change, and requires a GPU texture allocation. The Expo GL probe exercises the same image and clip through R3F Native's image loader and useFrame; Android and iOS results are emitted by the Native GPU workflow. The deterministic Node report uses pinned pixels and a sampled pose. External image URLs, compressed textures, and larger glTF assets remain separate integration cases.

The weekly/on-demand Native workflow executes those same fixtures through the portable ThreeCanvas entry on React Native Android and the Skia Canvas host. It uploads screenshots, accessibility trees, and three-native-corpus.json: the four portable archetypes must draw, expose named Android controls, and activate; the ordinary glTF/PBR fixture must stop with the documented UNSUPPORTED_MATERIAL diagnostic. This is Native-host evidence, not a claim that the separately tracked Native GPU backend is available.


Part of Hozo, a Rust-powered universal UI compiler and accessibility-first layer for React Native. Most applications install @hozo/core and one build integration; see Getting started. Source and issues: github.com/iray-tno/hozo.