Hosting a spinner¶
Warning
Online spinner publishing is available to PRO subscribers.
The Webex Spinner SDK renders exported one-axis or two-axis product-image sequences directly into a canvas. Keep the SDK and image URLs on HTTPS, and pin the SDK to an immutable release.
Add the SDK¶
<script src="https://cdn.jsdelivr.net/gh/randomcontrol/[email protected]/webex-spinner.min.js"></script>
The classic build exposes one global: WebexSpinner. Package consumers can instead import the default class and the events constant from @randomcontrol/webex-spinner.
Create the canvas¶
<div class="spinner-shell" aria-busy="true">
<canvas id="product-spinner" width="800" height="800"></canvas>
<progress id="spinner-progress" max="1" value="0"></progress>
</div>
.spinner-shell { max-width: 800px; margin-inline: auto; }
.spinner-shell canvas { display: block; width: 100%; height: auto; cursor: grab; }
.spinner-shell[aria-busy="true"] canvas { opacity: 0.45; pointer-events: none; }
Mount the spinner¶
const canvas = document.querySelector('#product-spinner');
const shell = document.querySelector('.spinner-shell');
const progress = document.querySelector('#spinner-progress');
const images = Array.from({ length: 34 }, (_, index) =>
`/products/ring/atlas_${String(index).padStart(4, '0')}.webp`
);
const spinner = new WebexSpinner(canvas, {
images,
atlasColumns: 4,
atlasRows: 1,
poster: '/products/ring/poster.webp',
framesPerSecond: 30,
autoPlay: true,
loop: 'auto',
draggable: true,
preloadAmount: 8,
});
spinner.addEventListener(WebexSpinner.events.loadProgress, ({ detail }) => {
progress.value = detail.progress;
});
spinner.addEventListener(WebexSpinner.events.loadError, ({ detail }) => {
console.error(`Could not load ${detail.url}`);
});
await spinner.ready;
shell.removeAttribute('aria-busy');
progress.remove();
Events belong to the Spinner instance, not the canvas. The ready promise resolves after every image in the active sequence has either loaded or reported an error.
Two-axis sequences¶
Pass one atlas array per pitch row and describe the angular grid:
const spinner = new WebexSpinner(canvas, {
images: [
['/ring/pitch-30-atlas-1.webp', '/ring/pitch-30-atlas-2.webp'],
['/ring/pitch-20-atlas-1.webp', '/ring/pitch-20-atlas-2.webp'],
['/ring/pitch-10-atlas-1.webp', '/ring/pitch-10-atlas-2.webp'],
],
atlasColumns: 4,
atlasRows: 1,
pitchMinDegrees: -30,
yawMinDegrees: -180,
stepDegrees: 10,
initialPitchIndex: 1,
});
Every pitch row must contain the same number of atlases. stepDegrees is shared by yaw and pitch.
Replace a sequence¶
Material or product changes do not require rebuilding the instance:
await spinner.setSequence({
images: roseGoldImages,
atlasColumns: 4,
atlasRows: 1,
poster: '/products/ring/rose-gold-poster.webp',
});
setSequence() validates before changing state, cancels the old preload, preserves the nearest view, and returns the new readiness promise.
API reference¶
Options¶
| Option | Default | Description |
|---|---|---|
images | required | Atlas URLs as string[] or pitch rows as string[][]. |
atlasColumns | 4 | Horizontal tiles per atlas. |
atlasRows | 1 | Vertical tiles per atlas. |
poster | false | Still shown before the first sequence frame. |
pitchMinDegrees | 0 | Pitch represented by row zero; required for multiple pitch rows. |
yawMinDegrees | 0 | Yaw represented by frame zero. |
stepDegrees | 0 | Angular step; required for multiple pitch rows. |
initialYawIndex | null | Initial yaw index. |
initialPitchIndex | null | Initial pitch index. |
preloadAmount | 0 | Atlas count in the preview batch. |
framesPerSecond | 30 | Automatic playback rate. |
loop | 'auto' | Automatic, forced, or disabled yaw wrapping. |
autoPlay | true | Play when ready. |
reverse | false | Begin in reverse. |
draggable | true | Enable pointer interaction. |
dragSensitivity | 0.5 | Horizontal drag sensitivity. |
pitchDragSensitivity | null | Vertical sensitivity; null matches angular sensitivity automatically. |
touchSensitivityMultiplier | 1 | Touch-input multiplier. |
axisLockThreshold | 10 | Pixels before a two-axis gesture locks to its dominant axis. |
invertHorizontalDrag | false | Reverse horizontal drag. |
invertVerticalDrag | false | Reverse vertical drag. |
touchBehavior | 'auto' | Preserve page scrolling where possible, or use 'capture'. |
pageScrollLockDuration | 1500 | Milliseconds to guard against an accidental page scroll after interaction. |
crossOrigin | null | Image CORS mode. |
Methods and state¶
- Playback:
play(),pause(),setDirection(),setFrameRate(),setLoop(). - Interaction:
setDraggable(),setPitch(),resetPitch(). - Content:
setSequence()anddestroy(). - Readonly state:
ready,currentFrame,totalFrames,totalImages,running,dragging,loaded,loadedWithErrors,yawIndex,yawAngle,pitchIndex,pitchAngle,yawCount, andpitchCount.
Events¶
Use WebexSpinner.events.loadProgress, loadError, previewReady, ready, and posterReady. loadError includes the URL, pitch index, atlas index, and attempt count.
Troubleshooting¶
- If images fail across origins, configure the image host's CORS policy and set
crossOrigin: 'anonymous'when canvas pixel reads are required. - Match
atlasColumnsandatlasRowsto the exported atlas layout. - Keep the canvas responsive with CSS; its width and height attributes control render resolution.
- Call
destroy()before permanently removing a spinner from the page.