Try-On in the Mini Viewer
The iJewel Mini Viewer can host the try-on experience directly, so you don't have to build a WebGi viewer yourself. Depending on how much control you need, pick one of three approaches.
| You want to | Use | Working template |
|---|---|---|
| Add try-on with zero JavaScript | Option 1 - iframe embed | Open live · Source |
| Show the built-in try-on button in your own page | Option 2 - Mini Viewer SDK with viewer options | Open live · Source |
| Build your own try-on button, finger picker and branding | Option 3 - Mini Viewer SDK with a configured plugin | Open live · Source |
Each template is a complete, self-contained page you can copy: swap in your own file ID and Drive instance, serve it over HTTPS, and it works. All of them are in iJewelTemplates.
Before you start: configure the model
Do this first
None of the three options below will show a try-on button until the model has been set up for try-on in the editor. This is not something you can switch on from code - the placement has to be authored per model, and the viewer reads it from the saved model.
Open the model in the editor (in iJewel Drive or in iJewel Playground) and:
- Open the TryOn Settings tab - the AR icon in the tab rail on the right.
- Turn on the Enable AR switch. This unlocks the rest of the panel and drops you straight into setup mode, where a virtual sizing cylinder appears in the scene.
- Fit the ring around that cylinder using the size, position and rotation controls. Auto Fit analyses the model and snaps it to a sensible starting position - click it again to cycle through alternatives, which is what the
(1/3)counter on the button is telling you. - Click Preview in AR to check the fit on a real hand.
- Save the model.
The rest of the panel is there to make that fit easier:
| Control | What it does |
|---|---|
| Enter Setup / Exit Setup | Toggles setup mode. Enabling AR enters it for you; the transform controls are greyed out whenever you leave it. |
| Viewpoint: Front / Top / Side | Snaps the camera to an orthographic view. Judging rotation by eye is much easier from a fixed angle. |
| Size, position, rotation | The placement itself. Rotation is shown in degrees here even though it is stored in radians. |
| Auto Fit | Estimates ring size and placement from the geometry. Repeated clicks cycle through the candidates it found. |
| SS Reflection, SS Ambient Occlusion | Render quality during try-on only, each with intensity and radius. These are saved with the model and travel to the viewer. |
HTTPS is required
Try-on also needs your page served over HTTPS. Browsers block camera access on plain HTTP, so the button will appear but fail when pressed.
Option 1: iframe embed
Don't write the iframe by hand - the editor generates it for you, already pointing at the right model with the right permissions.
In the same editor session where you configured try-on, open the Embed tool (the </> icon), pick your size and viewer controls, and click Copy HTML code. Paste the result into your page. The full walkthrough, including how to reach the editor from Drive or iJewel Design, is in Create an iJewel viewer embed.
The generated snippet looks like this:
<iframe title="Solitaire Ring"
frameborder="0"
mozallowfullscreen="true"
webkitallowfullscreen="true"
xr-spatial-tracking
execution-while-out-of-viewport
execution-while-not-rendered web-share
allow="camera; autoplay; fullscreen; xr-spatial-tracking; web-share"
width="1280px" height="720px"
src="https://ijewel3d.com/drive/files/{MODEL_ID}/embedded"></iframe>Nothing in it is try-on specific: the embed page offers try-on automatically for any model that has it enabled, so the only thing that turns the button on is the editor setup above.
Keep the camera permission
An iframe does not inherit camera access from the parent page, and the generated code already carries allow="camera; …" for exactly that reason. If you retype the tag by hand, or your CMS strips unknown iframe attributes, the model still renders but the try-on button fails the moment a visitor taps it.
If your CMS only accepts a URL rather than HTML, copy just the value of src - that page shows the same viewer, try-on included. Depending on where the model lives, the editor produces either a Drive URL as above, or an iJewel Design one of the form https://ijewel.design/embedded?slug=MODEL_SLUG. Enterprise customers get the URL of their own Drive instance.
Trade-off: nothing to install and nothing to maintain, but the try-on UI is the stock iJewel one and your page cannot start, stop or observe the session.
Working template
Open live · View source — a product page built around the embed, with no JavaScript of its own.
Option 2: Mini Viewer SDK with viewer options
Load the Mini Viewer yourself and turn the built-in try-on button on through ViewerOptions. The viewer downloads the try-on library on demand - the first time the user presses the button - so it costs nothing on initial page load.
<!-- Load webgi first, then the mini-viewer (nowebgi build) -->
<script src="https://releases.ijewel3d.com/libs/webgi-v0/bundle-0.22.0.js"></script>
<script src="https://releases.ijewel3d.com/libs/mini-viewer/0.6.11/bundle.nowebgi.iife.js"></script>
<div id="viewer" style="width: 100%; height: 100vh;"></div>
<script>
ijewelViewer.loadModelById(
"MODEL_ID", // iJewel Drive file ID
"drive", // your Drive basename
document.getElementById("viewer"),
{
hideTryOn: false, // show the try-on button - hidden by default
showCard: false,
},
);
</script>Two viewer options control this:
| Option | Type | Default | Description |
|---|---|---|---|
hideTryOn | boolean | true | Hides the try-on button in the viewer's button bar. Set to false to show it. The button still only appears when the model has been configured for try-on. |
tryonPath | string | pinned build | URL of the web-vto script to load when the user starts try-on. Override it to pin a specific version. |
Set tryonPath explicitly
The viewer's built-in default is pinned to an older web-vto release and only moves when the viewer itself is republished. Pointing tryonPath at the version you tested against means try-on stops changing under you, and lets you pick up fixes without waiting for a viewer release.
To pin the try-on library version:
{
hideTryOn: false,
tryonPath: "https://releases.ijewel3d.com/libs/web-vto/0.2.9/web-vto.js",
}The same options work with the React ViewerComponent via its viewerOptions prop:
<ViewerComponent
modelId={modelId}
viewerOptions={{ hideTryOn: false, showCard: false }}
/>Trade-off: one flag gets you a working, camera-permission-aware try-on flow with loading and error states - but the button, its position and its styling are the viewer's.
During a session the viewer offers Flip Camera and Exit AR, and nothing else. There is no finger picker: the ring stays on whichever finger the model was authored for. If customers need to move it between fingers, that is the reason to go to Option 3.
Working template
Open live · View source — the whole integration is the loadModelById call in script.js.
Option 3: Mini Viewer SDK with a configured plugin
For full control, keep the viewer's own button hidden and add the try-on plugins yourself once the viewer is ready. This is the option to use when try-on has to be triggered from your own product page UI, or when the loading screen has to carry your branding.
<!-- webgi, then web-vto, then the mini-viewer -->
<script src="https://releases.ijewel3d.com/libs/webgi-v0/bundle-0.22.0.js"></script>
<script>window.webgi = window;</script>
<script src="https://releases.ijewel3d.com/libs/web-vto/0.2.9/web-vto.js"></script>
<script src="https://releases.ijewel3d.com/libs/mini-viewer/0.6.11/bundle.nowebgi.iife.js"></script>Script order matters
web-vto reads webgi off window.webgi, so the window.webgi = window line must sit between the two script tags. Loading web-vto before webgi throws at parse time.
<div id="viewer" style="width: 100%; height: 100vh;"></div>
<div id="tryon-controls">
<button id="tryon-toggle" disabled>Start Try-On</button>
<button id="tryon-flip" disabled>Flip camera</button>
<button id="tryon-finger" disabled>Next finger</button>
</div>const FINGERS = ["index", "middle", "ring", "pinky"];
let fingerIndex = 2;
let driveFile;
// The Drive file data carries the try-on settings authored in the editor.
window.addEventListener("ijewel-file-data", ({ detail }) => {
driveFile = detail.iJewelFileData;
}, { once: true });
window.addEventListener("ijewel-viewer-ready", async ({ detail }) => {
const viewer = detail.viewer;
const tryonConfig = readTryonConfig(driveFile);
if (!tryonConfig?.enabled) return; // model isn't set up for try-on
// Hide the entry point on devices that cannot run try-on well.
const support = ij_vto.canRunVTO();
if (!support.ok) {
console.warn("Try-on unavailable:", support.reason, support.details);
return;
}
// Add the plugin and apply the placement authored in the editor.
const tryon = await viewer.addPlugin(ij_vto.RingTryonPlugin, { preload: true });
tryon.fromJSON({ ...tryonConfig, type: ij_vto.RingTryonPlugin.PluginType });
// Add the loading / error / hand-prompt UI, branded for your store.
await viewer.addPlugin(new ij_vto.TryonUIPlugin({
loading: {
title: "Preparing your try-on",
subText: null,
logo: "https://example.com/logo.svg",
align: "center",
},
handPrompt: { text: "Show the back of your hand" },
}));
wireControls(tryon);
});
// The settings can live on the file itself or be inherited from its folder.
function readTryonConfig(file) {
const parse = (value) =>
!value ? {} : typeof value === "string" ? JSON.parse(value) : value;
const own = parse(file?.config);
const inherited = parse(file?.defaultConfig);
return own.tryonConfig ?? inherited.tryonConfig;
}
function wireControls(tryon) {
const toggle = document.getElementById("tryon-toggle");
const flip = document.getElementById("tryon-flip");
const finger = document.getElementById("tryon-finger");
const sync = () => {
toggle.disabled = false;
toggle.textContent = tryon.running ? "Exit Try-On" : "Start Try-On";
flip.disabled = finger.disabled = !tryon.running;
};
toggle.onclick = async () => {
tryon.running ? await tryon.stop() : await tryon.start();
sync();
};
flip.onclick = () => tryon.flipCamera();
finger.onclick = () => {
fingerIndex = (fingerIndex + 1) % FINGERS.length;
tryon.finger = FINGERS[fingerIndex];
};
tryon.addEventListener("stop", sync);
tryon.addEventListener("error", ({ detail }) => {
console.error("Try-on error:", detail.reason, detail.error);
sync();
});
sync();
}Load the model with the viewer's own try-on button hidden, so the only entry point is yours:
ijewelViewer.loadModelById(
"MODEL_ID",
"drive",
document.getElementById("viewer"),
{ hideTryOn: true, showCard: false },
);Preloading
viewer.addPlugin(ij_vto.RingTryonPlugin, { preload: true }) starts fetching the hand-tracking model and WASM as soon as the plugin is added, instead of when the user presses the button. It makes start() feel near-instant at the cost of a background download. Pass { preload: false } to defer it.
Trade-off: you own the button, the copy, the placement and the error handling - and you carry the web-vto script in your page weight whether or not the user tries the ring on.
Working template
Open live · View source — everything above assembled into a product page with its own try-on button, finger picker and error messages.
Where the settings come from
Whichever option you choose, the ring placement itself is authored in the editor and travels with the model as tryonConfig:
| Field | Description |
|---|---|
enabled | Whether try-on is offered for this model at all. |
modelScaleFactor | Uniform scale applied to the model in try-on. |
modelPosition | Position offset, as { x, y, z }. |
modelRotation | Rotation offset in radians, as { x, y, z } - the editor shows degrees but stores radians. |
ssr | Screen-space reflections - { enabled, intensity, radius }. |
ssao | Ambient occlusion and skin shading - { enabled, intensity, radius }. |
Options 1 and 2 apply these for you. Option 3 applies them with tryon.fromJSON(...), and you can override any of them afterwards - see the RingTryonPlugin API Reference.
Troubleshooting
| Symptom | Cause |
|---|---|
| Try-on button never appears | The model has no tryonConfig, or tryonConfig.enabled is false - it was never configured and enabled in the editor. |
| Button appears but nothing happens | hideTryOn is false but the page isn't served over HTTPS, or the iframe is missing allow="camera". |
ij_vto is not defined | The web-vto script hasn't loaded, or window.webgi = window is missing before it. |
| Ring floats off the finger | The placement was authored against a different model. Re-run setup mode for this model - see Edit Mode. |