Add an interactive 3D ring viewer to webpage
The Ring Viewer SDK renders a real-time 3D jewelry configurator inside any HTML element. This guide walks you through the complete integration — from loading the script to controlling metal tones, switching models, and cleaning up.
Prerequisites
Before you write any code, make sure you have these four credentials from TechcoreFor. They are required for the SDK to load models and render the 3D scene.
{{KEY}} — issued by TCC.canvasClientId. Never expose this in public source.bundle-*.js file hosted on S3 or a CDN.How it works
The SDK bundle, when loaded, attaches a global object called window.techcoreFor to the page.
You call techcoreFor.init() with your credentials and a container element.
It returns a ring instance — an object with methods to change metal tones, load different ring models, control the camera, and more.
The SDK renders into a WebGL canvas it creates inside your container. You control the size of that canvas entirely through CSS on your container element.
Load the SDK script
Add a <script> tag pointing to your SDK bundle URL.
The script attaches the window.techcoreFor global once it finishes loading.
Option A — Static script tag
<!-- Place before your closing </body> tag --> <script src="https://your-cdn.example.com/bundle-forevermark.js"></script> <script> window.addEventListener('load', () => { const sdk = window.techcoreFor; // sdk is ready — proceed to Step 3 }); </script>
Option B — Dynamic loader (recommended for JS frameworks)
let _sdkPromise = null; function loadRingSDK(bundleUrl) { if (window.techcoreFor) return Promise.resolve(window.techcoreFor); if (_sdkPromise) return _sdkPromise; _sdkPromise = new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = bundleUrl; script.async = true; script.addEventListener('load', () => window.techcoreFor ? resolve(window.techcoreFor) : reject(new Error('techcoreFor not defined after load')) ); script.addEventListener('error', () => reject(new Error(`Failed to load SDK from ${bundleUrl}`)) ); document.head.appendChild(script); }); return _sdkPromise; }
_sdkPromise variable ensures the <script> tag is injected only once, even if multiple components call loadRingSDK() simultaneously.Create a container element
The SDK renders a WebGL canvas inside whatever element you give it.
Create a <div> and size it with CSS — the canvas fills 100% of it.
<div id="ring-viewer" style="width: 600px; height: 500px;"></div>
px, %, vh, or responsive breakpoints.
For full-page viewers, width: 100%; height: 100vh works well. No minimum size is imposed.
How the Canvas & GLB work
Understanding this flow helps you size the viewer correctly, serve model files efficiently, and debug loading issues before they reach production.
When you call techcoreFor.init(),
the SDK automatically creates a <canvas>
element and appends it inside your container. You do not create the canvas yourself — the SDK owns it.
The canvas always fills 100% of the container's width and height. To resize the viewer, resize the container:
/* Fixed size */ #ring-viewer { width: 600px; height: 500px; } /* Responsive — fills the column, fixed height */ #ring-viewer { width: 100%; height: 480px; } /* Full viewport */ #ring-viewer { width: 100vw; height: 100vh; }
ResizeObserver. Padding shrinks the usable area; use a wrapper element for visual chrome instead.
Each ring model is a .glb file — a binary-encoded glTF 2.0 package
that bundles the 3D mesh, PBR materials, and textures into a single download.
The SDK fetches the correct .glb from your CDN automatically
when you call init() or ring.loadModel().
SDK fetches
models.json and resolves the model ID
to a .glb URL.
The binary is fetched from your CDN over HTTPS. Progress fires
onLoadProgress(0…1).
Mesh geometry and textures are uploaded to the GPU. The canvas renders the first frame and
onLoad() fires.
User can orbit, zoom, and switch metal tones in real time.
models.json and .glb files from the browser.
Set Access-Control-Allow-Origin: * (or your domain) on those responses,
otherwise the fetch will be blocked and onError fires.
Initialize the viewer
Call techcoreFor.init() with your credentials, container, and first model.
It returns a Promise that resolves to a ring instance used for all further controls.
const container = document.getElementById('ring-viewer'); const ring = await window.techcoreFor.init({ // Required container: container, secretKey: 'YOUR_KEY_HEX.YOUR_IV_HEX', canvasClientId: 'YOUR_CANVAS_CLIENT_ID', canvasSecretKey: 'YOUR_CANVAS_SECRET_KEY', model: '15271', metalTone: 'WG', // 'WG' | 'YG' | 'RG' // Optional — loading callbacks onLoadStart: () => showSpinner(true), onLoadProgress: (p) => setProgress(Math.round(p * 100)), onLoad: () => showSpinner(false), onError: (err) => console.error(err), }); console.log('Available models:', ring.availableModels);
ring.availableModels after init. This is the live list of IDs pulled from your catalog. Always use these IDs when calling ring.loadModel().Change the metal tone
Call ring.setMetalTone() to switch between
'WG' (White Gold),
'YG' (Yellow Gold), and
'RG' (Rose Gold).
document.getElementById('btn-white-gold').addEventListener('click', () => ring.setMetalTone('WG') ); document.getElementById('btn-yellow-gold').addEventListener('click', () => ring.setMetalTone('YG') ); document.getElementById('btn-rose-gold').addEventListener('click', () => ring.setMetalTone('RG') );
Switch between ring models
Call ring.loadModel() with a model ID from ring.availableModels.
The current metal tone is preserved automatically.
async function switchModel(modelId) { if (!ring.availableModels.includes(modelId)) { console.warn(`Model "${modelId}" is not in availableModels`); return; } await ring.loadModel(modelId); }
loadModel() is async and can take a moment — set a loading indicator before the await and clear it after.Other viewer controls
The ring instance exposes camera views, auto-rotate, and screenshot capture.
// Camera view — 'perspective' | 'front' | 'side' await ring.setView('perspective'); await ring.setView('front'); await ring.setView('side'); // Auto-rotate ring.setAutoRotate(true); // start ring.setAutoRotate(false); // stop ring.setAutoRotate(true, 0.5); // custom speed // Screenshot → download as PNG const blob = await ring.screenshot(); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'ring.png'; a.click(); URL.revokeObjectURL(a.href);
Clean up when done
Call ring.dispose() when the viewer is removed from the page.
This releases the WebGL context and stops background work, preventing memory leaks.
function teardown() { if (ring) { ring.dispose(); ring = null; } } // React: return () => teardown() (inside useEffect) // Vue: beforeUnmount() { teardown() } // Vanilla: window.addEventListener('beforeunload', teardown)
Init options reference
All options accepted by techcoreFor.init().
| Option | Type | Required | Description |
|---|---|---|---|
| container | Element | string | required | DOM element or CSS selector the canvas is rendered into. |
| secretKey | string | required | AES-CBC key in {{KEY}} format, issued by TCC. |
| canvasClientId | string | required | canvasX runtime license client ID. |
| canvasSecretKey | string | required | Secret key paired with canvasClientId. |
| model | string | required | First model to load — must be a valid ID from your TCC catalog. |
| metalTone | 'WG' | 'YG' | 'RG' | required | Starting metal tone. |
| view | 'perspective' | 'front' | 'side' | optional | Initial camera angle. Defaults to 'perspective'. |
| baseUrl | string | optional | Asset base URL. Defaults to TCC production CDN. |
| catalogPath | string | optional | Catalog JSON path relative to baseUrl. Default: "models.json". |
| renderScale | number | optional | Resolution multiplier. Default 1. Use 0.9 on low-end devices. |
| camera | object | optional | minDistance, maxDistance, maxPolarAngle constraints. |
| onLoadStart | () => void | optional | Called when loading begins. |
| onLoadProgress | (p: number) => void | optional | Progress from 0 to 1. |
| onLoad | () => void | optional | Called when model is fully visible. |
| onError | (err: Error) => void | optional | Called on init or load failure. |
Instance API
Methods and properties on the ring object returned by init().
'WG', 'YG', or 'RG'.ring.availableModels.'perspective', 'front', or 'side'.speed multiplier.Complete minimal example
A self-contained HTML page — replace the placeholder credentials with your own.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Ring Viewer</title> <style> body { margin:0; background:#111; color:#fff; font-family:sans-serif; } #viewer { width:100vw; height:70vh; } .controls { display:flex; gap:12px; padding:16px; flex-wrap:wrap; } button { padding:10px 20px; border:none; border-radius:3px; cursor:pointer; background:#222; color:#fff; font-size:14px; } button:hover { background:#333; } #status { padding:8px 16px; font-size:13px; color:#888; } </style> </head> <body> <div id="viewer"></div> <div id="status">Loading…</div> <div class="controls"> <button id="wg">⬜ White Gold</button> <button id="yg">🟡 Yellow Gold</button> <button id="rg">🔴 Rose Gold</button> <button id="rotate">↻ Auto-Rotate</button> <button id="shot">📷 Screenshot</button> </div> <script src="https://your-cdn.example.com/bundle-forevermark.js"></script> <script> let ring = null, rotating = false; const status = document.getElementById('status'); window.addEventListener('load', async () => { try { ring = await window.techcoreFor.init({ container: document.getElementById('viewer'), secretKey: 'XXXXXXXXxx.XXXXXXxxxx', canvasClientId: 'XXXXXXXXXXXXXxxxxxxxXXXXXXXXXXXX', canvasSecretKey: 'XXXXXXXXXXXXXxxxxxxxXXXXXXXXXXXX', model: '15271', metalTone: 'WG', onLoadStart: () => { status.textContent = 'Loading model…'; }, onLoad: () => { status.textContent = 'Ready ✓'; }, onError: (err) => { status.textContent = 'Error: ' + err.message; }, }); document.getElementById('wg').onclick = () => ring.setMetalTone('WG'); document.getElementById('yg').onclick = () => ring.setMetalTone('YG'); document.getElementById('rg').onclick = () => ring.setMetalTone('RG'); document.getElementById('rotate').onclick = () => ring.setAutoRotate(rotating = !rotating); document.getElementById('shot').onclick = async () => { const blob = await ring.screenshot(); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'ring.png'; a.click(); URL.revokeObjectURL(a.href); }; } catch (err) { status.textContent = 'Init failed: ' + err.message; } }); window.addEventListener('beforeunload', () => ring?.dispose()); </script> </body> </html>