Skip to content

Viewer SDK

The Maverick Excelsior Viewer SDK puts an interactive .webex scene inside your web page. You need only basic HTML and JavaScript: a container, one SDK script, an API key, and a scene exported from Excelsior.

Before you start

You need a Viewer SDK API key whose allowlist includes your website, a .webex file exported with Export Interactive Scene, and a web server. Opening the HTML directly as file:///... is unsupported; for local work, run npx serve . in the page's directory.

Host the scene beside the page while getting started. A scene on another origin needs that server's Access-Control-Allow-Origin header to permit your website.

Five-minute CDN setup

Create a folder containing index.html and ring.webex, then put this in index.html. Replace the API key and pinned SDK version with the values supplied to you.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My Excelsior viewer</title>
  <style>
    html, body, #viewer { width: 100%; height: 100%; margin: 0; }
    #viewer canvas { width: 100%; height: 100%; display: block; }
  </style>
</head>
<body>
  <div id="viewer"></div>
  <script src="https://cdn.jsdelivr.net/gh/randomcontrol/webex-viewer@v3.0.0/webex-viewer.js"></script>
  <script>
    WebexViewer.mount({
      container: '#viewer',
      apiKey: 'ak_xxxxxxxxxxxxxxxx',
      scene: './ring.webex',
      background: 'white',
      quality: 'high'
    }).then(function (viewer) {
      console.log('Scene ready', viewer);
    }).catch(function (error) {
      console.error(error.code, error.message);
    });
  </script>
</body>
</html>

Run npx serve ., open the URL it prints, and check the browser developer console if the scene does not appear. The usual causes are a wrong scene path, an API-key allowlist that does not include the page's domain, CORS restrictions, or mixed SDK versions.

Pin one immutable release

Always use a complete tagged release such as @v3.0.0; do not use @latest, a branch, or unversioned jsDelivr URLs. The SDK, engine JavaScript, WASM, and data file must come from the same release.

Self-hosting

Copy these runtime files from one release into a directory and load its webex-viewer.js:

viewer/
├── webex-viewer.js
├── webex-viewer.mjs
├── webex-viewer-engine.js
├── webex-viewer-engine.wasm
└── webex-viewer-engine.data
<script src="./viewer/webex-viewer.js"></script>

The SDK finds the engine companions relative to its own URL. Keep their filenames and directory relationship intact.

ES modules

Modern applications may import the module build. It creates no global variables.

<script type="module">
  import { WebexViewer } from './viewer/webex-viewer.mjs';

  const viewer = await WebexViewer.mount({
    container: '#viewer',
    apiKey: 'ak_xxxxxxxxxxxxxxxx',
    scene: './ring.webex'
  });
</script>

Mount options

Option Meaning
container Element or selector into which the SDK creates a canvas.
canvas Existing canvas or selector; use this instead of container.
apiKey Viewer SDK API key.
scene Optional scene URL to load before mount() resolves.
baseUrl Directory containing the engine files; normally auto-detected.
engine Alternate engine JavaScript filename or URL.
background Initial color, such as white or #f4f4f4.
quality low, medium, high, or ultra.
autospin Initial automatic rotation state; this is a startup option.
infotag Initial information-overlay state.
shortcode Hosted-viewer shortcode, when applicable.
loadTimeout Scene-load timeout in milliseconds.
contextAttributes WebGL attributes, for example { alpha: true }.
onEvent Callback receiving every raw viewer event.

mount() resolves after the engine and optional scene are ready. It rejects with a WebexViewerError; error.code is the stable machine-readable reason.

Main methods

await viewer.load('./another.webex');
viewer.close();

viewer.setMaterial('Metal 01', 'Yellow Gold 18k');
viewer.setProperty('::globals', 'globals_pose_id', '2');
const hfov = await viewer.getProperty('::camera', 'tracker_cam_hfov');

viewer.setQuality('ultra');
viewer.setBackground('#f0f0f0');
viewer.zoom('+1');
viewer.commands.centerView();

const jpegDataUrl = await viewer.capture();
viewer.pushTrackingPose(packet); // High-frequency virtual try-on pose path.
viewer.destroy();

destroy() stops the runtime, rejects unfinished operations, removes an SDK-created canvas, and is safe to call more than once.

The command inventory is the complete reference for viewer.commands. Prefer the higher-level methods shown above when one is available. Layer visibility is an ordinary property:

viewer.setProperty('Metal 01', 'obj_visible', '0');

Events

viewer.on('load-progress', ({ percentage }) => {
  progress.textContent = Math.round(percentage) + '%';
});
viewer.on('scene-ready', () => { controls.disabled = false; });
viewer.on('error', ({ error }) => console.error(error.code, error.message));
viewer.on('event', event => console.debug(event.type, event.d0, event.d1, event.d2));

These examples register listeners for later viewer.load() calls. If scene is supplied to mount(), that initial scene is ready before the mount promise resolves. See the event inventory for every stable event name, its payload, and subscription rules. Multipart methods report completion and failure through their returned promises.

Multipart scenes

The SDK accepts a URL, Blob, ArrayBuffer, or typed-array view. Reusing a part ID atomically replaces that part.

await viewer.load('./scenes/base.webex');
await viewer.mergePart('head', './scenes/head-round.webex', { format: 'webex' });
await viewer.mergePart('engraving', glbBytes, { format: 'glb', filename: 'name.glb' });
await viewer.removePart('head');
await viewer.clearParts();

Working examples

The examples overview links to live, view-source-friendly integrations and explains what each one teaches, from a minimal mount to multipart generation.

Found a mistake?

If anything here is wrong or unclear, please contact us and we will fix it.