@virtualdisplay.io/client
v4.0.0-alpha.7
Published
TypeScript-first 3D model integration library with iframe-based architecture
Maintainers
Readme
Virtualdisplay client
TypeScript library for embedding interactive 3D product models in web applications.
- Built-in viewer UI with loading indicator, product controls, and action bar
- Simple attribute-based API for product configurators
- Server-driven configuration, no client-side mapping needed
- Fire-and-forget messaging with automatic state synchronisation
- Zero external dependencies (all bundled)
- Works with any frontend framework or vanilla JavaScript
Installation
npm install @virtualdisplay.io/client
# or
pnpm add @virtualdisplay.io/client
# or
yarn add @virtualdisplay.io/clientQuick start
Simple product (no options)
import { VirtualdisplayClient } from '@virtualdisplay.io/client';
// Your 3D model is now displayed
const client = new VirtualdisplayClient({
parent: '#product-container',
license: 'your-license-key',
model: 'statue-model',
});Configurable product (with options)
import { VirtualdisplayClient } from '@virtualdisplay.io/client';
const client = new VirtualdisplayClient({
parent: '#product-container',
license: 'your-license-key',
model: 'sneaker',
});
// Select a product option. The 3D model updates automatically.
// Calls are buffered until the server provides the attribute mapping
client.select('Color', 'red');The server defines which attributes and values are available for each model. The client receives this configuration automatically via postMessage. No manual mapping required.
How it works
The 3D server hosts the viewer in an iframe, keeping WebGL complexity isolated from your application. The client library handles all communication via postMessage.

On startup, the server sends the client its configuration (viewer settings, camera limits) and available product attributes. The loading indicator shows progress while the model downloads.

- Client creates iframe; outgoing messages are queued until the server is ready.
- Server sends
HANDSHAKEwhen its message listener is active; client flushes queue. - Server sends progress updates while the model downloads. The client displays these in the loading indicator.
- Server sends
CONFIGwith viewer settings and camera limits from model metadata. - Server sends
MAPPINGwith attribute definitions, values, and defaults.onReadyfires.
Product configuration
Configure product options by selecting attribute values. The server defines which attributes exist (e.g. "Color", "Size") and which values each has (e.g. "red", "blue"). You select values; the 3D model updates automatically.

select()returns immediately; the result arrives asynchronously.- Each attribute in the result has its own
successflag and optionalerror. - Attributes within the same component fail as a group.
Selecting values
// Select a product option. The 3D model updates automatically.
client.select('Color', 'blue');
client.select('Size', 'large');Can be called immediately after construction. Selections are buffered until the server handshake completes.
Tracking changes
Keep your UI in sync with the 3D server state:
client.onChange('Color', (selectedValue) => {
// selectedValue.value is the selected value (e.g. 'blue')
// selectedValue.isSelected is true
updateColorButton(selectedValue.value);
});Querying available values
// All values for an attribute
const colors = client.getValues('Color');
colors.forEach((av) => {
console.log(av.value, av.isSelected);
});
// A specific value
const blue = client.getValue('Color', 'blue');
if (blue?.isSelected) {
highlightButton('blue');
}Direct attribute access
For advanced use cases, get the Attribute instance directly:
const colorAttr = client.getAttribute('Color');
if (colorAttr) {
colorAttr.select('blue');
colorAttr.getValues(); // AttributeValue[]
colorAttr.getValue('blue'); // AttributeValue | undefined
colorAttr.onChange((value) => { ... });
}Handling failed mutations
client.onMutationFailed(({ attribute, value }) => {
console.error(`Mutation failed: ${attribute} = ${value}`);
});Snapshots
Capture the current 3D view as an image. Useful for product shots, social sharing, or saving configurations.

- Filename is validated client-side (extension, no path traversal).
- Server always responds, either with image data or an error reason.
Taking a snapshot
const photo = client.snapshot.take('product-hero-shot.png');
// Register callback for when image is ready
photo.onDeveloped((photoData) => {
// photoData.filename - The filename you provided
// photoData.data - Base64 encoded image data
const img = document.createElement('img');
img.src = photoData.data;
document.body.appendChild(img);
});
// Handle errors (e.g., canvas not available)
photo.onError((error) => {
console.error('Snapshot failed:', error.message);
});Filename must end with .png. The server always returns PNG data.
Camera
The standard interface includes built-in camera controls when enabled by the operator in the portal. The camera can also be controlled programmatically.

- Commands accumulate via chaining;
.set()sends them all at once. .reset()sends immediately without chaining.- Position updates are continuous. They fire on both programmatic and user-driven camera movement.
Controlling the camera
// Chain commands, then send with .set()
client.camera.rotate(45).set();
client.camera.rotate(90).tilt(45).zoom(150).set();
// Reset to the default position configured in the portal (sends immediately)
client.camera.reset();Tracking camera position
The current camera position is updated in real-time as the user orbits, zooms, or tilts the model.
client.cameraPosition.onChange((position) => {
console.log(position.rotation, position.tilt, position.zoom);
});
// Read current values directly
console.log(client.cameraPosition.rotation);
console.log(client.cameraPosition.tilt);
console.log(client.cameraPosition.zoom);Standard interface
The client renders a complete viewer UI automatically: loading indicator, attribute controls, camera controls, and an action bar (AR, fullscreen).
Which controls are visible is configured by the model operator in the Virtualdisplay portal. The operator decides how the viewer looks and behaves, not the developer integrating the code. This way the operator can match the viewer to their customers' expectations: enabling AR for mobile visitors, choosing which product attributes to expose, or setting the default camera angle and language.
Lifecycle
Ready state
Use onReady to know when the model and attribute definitions are available.
Not required before calling select() or onChange(), those are buffered
automatically.
client.onReady(() => {
const values = client.getValues('Color');
console.log('Available colours:', values.map(v => v.value));
});Cleanup
client.destroy();Constructor options
interface ClientOptions {
parent: string | HTMLElement; // Where to embed the 3D viewer
license: string; // License key from your Virtualdisplay account
model: string; // Which 3D model to load
debug?: boolean; // Shows console logs for troubleshooting
language?: string; // Match the viewer language to the page: 'nl', 'en', or 'de'
}Error codes
The client throws VirtualdisplayError for known error conditions:
| Code | Meaning |
| --------------------- | ------------------------------------------------ |
| NO_MAPPING | Attribute data not yet available from the server |
| ATTRIBUTE_NOT_FOUND | Requested attribute doesn't exist in the mapping |
| VALUE_NOT_FOUND | Requested value doesn't exist for attribute |
| PARENT_NOT_FOUND | Parent element for iframe not found |
| INVALID_PARAMETER | Invalid parameter (e.g. snapshot filename) |
| SNAPSHOT_FAILED | Server could not capture the snapshot |
Troubleshooting
Iframe not loading:
- Check that the parent element exists in the DOM
- Verify your license key is valid
- Ensure the model ID matches your licence
Attributes not working:
- Verify attribute names match exactly (case-sensitive)
- Check that the value exists for the attribute
- Use
onMutationFailedto catch server-side errors
Changelog
See the npm version history for release notes.
