@fiddle-digital/jitzy-three
v0.0.1
Published
Three.js bridge for projecting world-space anchors into @fiddle-digital/jitzy targets.
Readme
Jitzy Three
Jitzy Three is the Three.js bridge for Jitzy. Pin markers to meshes and world-space points, connect them with animated lines, and let those lines live inside your 3D scene — depth-tested, occluded by geometry, and always in the right perspective.
Built by Fiddle.Digital.
npm install @fiddle-digital/jitzy-threeOverview
What is Jitzy Three?
Jitzy Three connects the two worlds: a Three.js scene on one side, the Jitzy overlay on the other. It projects 3D anchors onto the screen every frame, keeps Jitzy targets in sync with them, and renders connectors as real scene objects when both ends have a position in 3D space. Mix in plain HTML elements — cards, labels, panels — and connect them to 3D objects just as easily.
Key Features
- 3D Anchors: Attach a node to an
Object3D, a fixed world position, or a function that returns one. - HTML Nodes: Register a real DOM element as a node, and optionally give it a
depthso connectors touching it render in 3D. - Native 3D Connectors: Lines are real tube meshes in your scene, with true thickness, perspective, and depth testing.
- Occlusion Culling: Markers and connectors hide when they move behind opaque geometry.
- Offscreen Handling: Anchors outside the camera frustum are hidden automatically.
- Animated Reveals: Marker and connector transitions work in 3D too.
- Same API as Jitzy: Connect with
jitzy.connect(a, b)— the bridge only handles the 3D part. - Effortless Setup: One
mountThree()call and oneupdate()per frame.
Who is Jitzy Three for?
Jitzy Three is perfect for:
- Beginner Developers: Annotate a 3D model without learning projection math.
- Experienced Developers: Use the low-level sync API when you need a custom camera or projection.
- Designers: Combine HTML interface elements with 3D scenes, and tie them together visually.
Introduction
Why Jitzy Three?
Overlaying lines on a WebGL scene is deceptively hard. You have to project points every frame, hide them behind objects, handle off-screen anchors, and keep DOM elements and 3D positions in agreement. Jitzy Three takes care of all of it, so your scene stays focused on what it shows.
Benefits of Using Jitzy Three
- Simplicity: Add nodes, connect them, call
update()— no manual projection. - Depth-Aware Visuals: Connectors sit in the scene, so geometry correctly occludes them.
- Unified Workflow: 3D objects and HTML elements are both just nodes.
- Type Safety: Written in TypeScript, with every option typed.
Core Principles
- Jitzy stays in charge: Jitzy owns targets, connectors, and styles; the bridge supplies positions and a 3D renderer.
- Explicit connections: In 3D scenes you choose what connects to what.
- Opt-in depth: HTML nodes stay on the flat overlay until you give them a
depth.
Getting Started
Installation
npm install @fiddle-digital/jitzy @fiddle-digital/jitzy-three three@fiddle-digital/jitzy (>=0.0.1 <0.1.0) and three (>=0.150) are peer dependencies.
Quick Start
The overlay is a container positioned exactly over your Three.js canvas, with the same size.
<div style="position: relative">
<canvas id="scene"></canvas>
<div id="overlay" style="position: absolute; inset: 0; pointer-events: none"></div>
</div>import * as THREE from "three";
import { Jitzy } from "@fiddle-digital/jitzy";
import { mountThree } from "@fiddle-digital/jitzy-three";
// ...your renderer, scene, camera, and two meshes: meshA and meshB
// (unit cubes here, so z = 0.55 sits just above the front face)
const jitzy = Jitzy.mount(document.getElementById("overlay")!);
const three = mountThree(jitzy, camera, scene);
three.addNode({ id: "a", object: meshA, offset: [0, 0, 0.55], color: "#4488ff" });
three.addNode({ id: "b", object: meshB, offset: [0, 0, 0.55], color: "#44ddaa" });
jitzy.connect("a", "b", {
color: "rgba(255, 255, 255, 0.6)",
width: 1.5,
curvature: 0.3,
});
function animate() {
requestAnimationFrame(animate);
three.update(); // before render()
renderer.render(scene, camera);
}
animate();⚠ Note: Place a node on the surface of an opaque mesh, not inside it. With occlusion culling on (the default), a point at the center of a solid mesh is behind that mesh's own surface, so its marker is hidden. Use offset, a child Object3D on the surface, or set occlusionCulling: false.
⚠ Note: Call three.update() before renderer.render(). The bridge adds markers and connectors to your scene, so this frame's changes need to land before this frame is drawn — otherwise they show up one frame late.
When you're done:
three.dispose();
jitzy.dispose();API
mountThree(jitzy, camera, scene, options?)
Mounts the bridge on an existing Jitzy instance and returns a ThreeHandle.
| Option | Default | Description |
| ------------------ | ------- | ------------------------------------------------------------------------ |
| hideOffscreen | true | Hide anchors that fall outside the camera frustum |
| occlusionCulling | true | Hide markers and connectors once they are behind opaque scene geometry |
⚠ Note: mountThree() requires an already-mounted Jitzy instance and switches its topology to explicit, so connections are the ones you create with jitzy.connect().
ThreeHandle
| Method | Description |
| --------------------- | ------------------------------------------------- |
| three.addNode(config) | Register a node |
| three.removeNode(id) | Remove a node |
| three.update() | Project anchors and sync Jitzy — call every frame |
| three.dispose() | Remove all nodes registered through this handle |
addNode(config)
Give each node an id and one position source:
| Option | Type | Description |
| ------------- | ---------------------------- | -------------------------------------------------------------------- |
| id | string \| number | Node identifier, used when connecting |
| object | Object3D | Follow this object's world position |
| offset | [x, y, z] | Local offset from object, e.g. to sit on its surface |
| position | [x, y, z] | A fixed world position |
| getPosition | () => {x, y, z} | Compute the world position yourself, each frame |
| element | HTMLElement | Use a real DOM element instead of a 3D position |
| depth | number | For element nodes: distance from the camera, in world units |
Appearance and behavior:
| Option | Default | Description |
| -------------------------- | ------- | -------------------------------------------------------------------------- |
| color | — | Draws a colored dot marker. Without color, the node is an invisible connection point |
| visible | true | Set false for a connection point with no visible marker |
| markers | one centered marker ("default") | Custom marker positions and ids |
| styles | — | Full marker style override (takes precedence over color/visible) |
| hideWhenOffscreen | true | Hide this node when it leaves the frustum |
| markerAnimationConfig | — | Marker enter/exit animation |
| connectorAnimationConfig | — | Connector enter/exit animation |
Examples
Connect a 3D object to an HTML card
Registering an element with a depth lets connectors that touch it render in 3D:
three.addNode({
id: "card",
element: document.getElementById("card")!,
depth: 8, // world units from the camera
markers: [{ id: "default", xPercent: 0, yPercent: 50 }],
visible: false, // the card is the visual — no dot needed
});
three.addNode({ id: "planet", object: planetMesh, color: "#aa55ff" });
jitzy.connect("planet", "card", { color: "rgba(170, 85, 255, 0.5)", width: 1.5 });Without a depth, an element node stays on the flat 2D overlay and its connectors are drawn on top of the scene.
Fixed points and computed positions
three.addNode({ id: "north-pole", position: [0, 2, 0], color: "#ffffff" });
three.addNode({
id: "orbiter",
getPosition: () => ({ x: Math.cos(t) * 3, y: 0, z: Math.sin(t) * 3 }),
color: "#44ddaa",
});Invisible connection points
three.addNode({ id: "far-side", object: anchor, visible: false });
jitzy.connect("surface", "far-side", { curvature: 0.4 });Connecting nodes with custom markers
jitzy.connect("a", "b") links each node's marker with the id "default". If you define your own markers, address them explicitly:
jitzy.connect(
{ target: "a", marker: "out" },
{ target: "b", marker: "in" },
);Low-Level API
If you need your own camera setup or projection, the sync layer is available on its own:
import { createThreeSync } from "@fiddle-digital/jitzy-three";
const sync = createThreeSync(jitzy, {
container: overlayElement,
camera, // or: projectToNDC: (x, y, z) => ({ x, y, z })
scene,
});
sync.addAnchor({ id: "a", object3D: mesh, markers: [{ id: "default", xPercent: 50, yPercent: 50 }] });
sync.update();createThreeSync() returns addAnchor, removeAnchor, update, and dispose. It only syncs screen positions — to render connectors inside the scene, use mountThree().
Part of the Jitzy Ecosystem
| Package | Purpose |
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| @fiddle-digital/jitzy | The runtime engine |
| @fiddle-digital/jitzy-plugin-sdk | Build your own plugins, themes, and animations |
| @fiddle-digital/jitzy-three | The Three.js bridge (this package) |
License
© Fiddle.Digital. All rights reserved. This package is proprietary software.
Bring your scene's story to the surface with Jitzy Three.
