3D configurator integration overview
Table of content
This quick overview demonstrates how an Apviz 3D configurator can be integrated on any website.
Release
After having setup your 3D configurator using studio.apviz.io (or the GraphQL API) you will have to create an immutable snapshot of your 3D configurator using the release feature.
The integration URL associated with your release can then be copy/pasted right before the </body>
closing tag of your HTML page:
<!-- Non-blocking async script that will call "apvizShowcaseReady" once fully loaded. -->
<script
src="https://public.apviz.io/showcases/U0hPVzoxTFVNSVRkNGNx/releases/UkVMUzpJd1pNVGNvZmlv/main.js"
integrity="sha384-NiFFyIQE+q1fSRuall4N/x6C9YbjYxVNO7A9E0XnUIpes6p9w8bGj+B9Q/WpidQ3"
defer
crossorigin="anonymous"
data-apviz-callback="apvizShowcaseReady"
></script>
⚡️ By incorporating the defer
attribute in the script tag, Apviz ensures that the 3D viewer script is executed only after the HTML document is fully parsed, thereby preserving your page's SEO performance.
ℹ️ Note that integration URL may change for future release, so consider this URL as opaque and don't try to parse it or build it dynamically.
Basic integration (simple viewer)
The following code sample demonstrates how to integrate the Apviz 3D configurator into your website. The div
element serves as a customizable container for the 3D configurator. You can specify your own ID for this div
and use standard CSS to position and style it as needed.
<!doctype html>
<html>
<head>
<title>Apviz - Demo Ring</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
</style>
</head>
<body>
<!-- Container for the Apviz 3D viewer. -->
<div id="apviz-3d-viewer" style="width: 100%; height:100vh"></div>
<!-- Your custom initialization script. -->
<script>
async function apvizShowcaseReady(showcase) {
// Initialize a 3D viewer and bind it to the div.
const viewer = await showcase.createViewer({
divId : "apviz-3d-viewer"
});
// Initial configuration that MUST explicitly include ALL the fields.
// This will start displaying 3D.
await viewer.update({
fields : {
"carat" : "1ct",
"gem" : "diamond",
"gold" : "yellow"
}
});
}
</script>
<!-- Non-blocking async script that will call "apvizShowcaseReady" once fully loaded. -->
<script
src="https://public.apviz.io/showcases/U0hPVzoxTFVNSVRkNGNx/releases/UkVMUzpJd1pNVGNvZmlv/main.js"
integrity="sha384-NiFFyIQE+q1fSRuall4N/x6C9YbjYxVNO7A9E0XnUIpes6p9w8bGj+B9Q/WpidQ3"
defer
crossorigin="anonymous"
data-apviz-callback="apvizShowcaseReady"
></script>
</body>
</html>
The div
style attributes above are just examples; you are encouraged to apply your own styling.
ℹ️ Before calling the createViewer
function, please ensure that the client size of your div
container is at least 50x50px and that the CSS display
property is NOT set to none
. Failure to meet these conditions may result in unexpected WebGL context loss in some browsers. If you need to hide the viewer, the recommended approach is to use the CSS visibility
property set to hidden
or collapse
.
Integration with a menu
If you want to make your 3D configurable directly within your website you can bind your own menu to the Viewer.update
method:
<!doctype html>
<html>
<head>
<title>Apviz - Demo Ring</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
#my-menu { position: absolute; top: 0; left: 0; padding: 10px; background-color: #ffffff; }
</style>
</head>
<body>
<!-- Container for the Apviz 3D viewer. -->
<div id="apviz-3d-viewer" style="width: 100%; height:100vh"></div>
<!-- Your custom configuration menu. -->
<div id="my-menu">
<label for="carat">carat:</label>
<select id="carat">
<option>1ct</option>
<option>0.5ct</option>
</select>
<label for="gem">gem:</label>
<select id="gem">
<option>diamond</option>
<option>ruby</option>
</select>
<label for="gold">gold:</label>
<select id="gold">
<option>yellow</option>
<option>white</option>
</select>
</div>
<!-- Your custom initialization script. -->
<script>
async function apvizShowcaseReady(showcase) {
// Initialize a 3D viewer and bind it to the div.
const viewer = await showcase.createViewer({
divId : "apviz-3d-viewer"
});
// Initial configuration that MUST explicitly include ALL the fields.
// This will start displaying 3D.
await viewer.update({
fields : {
"carat" : "1ct",
"gem" : "diamond",
"gold" : "yellow"
}
});
// Menu initialization.
document.querySelectorAll("#my-menu select").forEach((select) =>
select.addEventListener("change", () =>
viewer.update({
fields: {
// After initialization, fields can be updated separately.
[select.id]: select.value
}
})
)
);
}
</script>
<!-- Non-blocking async script that will call "apvizShowcaseReady" once fully loaded. -->
<script
src="https://public.apviz.io/showcases/U0hPVzoxTFVNSVRkNGNx/releases/UkVMUzpJd1pNVGNvZmlv/main.js"
integrity="sha384-NiFFyIQE+q1fSRuall4N/x6C9YbjYxVNO7A9E0XnUIpes6p9w8bGj+B9Q/WpidQ3"
defer
crossorigin="anonymous"
data-apviz-callback="apvizShowcaseReady"
></script>
</body>
</html>
Marking image
When your product handles web-to-print capabilities such as flocking, printing, you can dynamically display an image on a predefined marking zone using Viewer.update
with the markingZones
parameter.
The image you want to set must be nested within the markingZones
parameter object using the marking zone key you want to target. If you need to update multiple zones at the same time, you can set multiple marking zone keys.
The provided image.color.source
value:
- Supports PNG, JPEG, GIF, SVG, BMP, and ICO image formats.
- May be any of those JavaScript types:
File
to get the image from an HTML input file type.HTMLImageElement
to get the image from an URL.ImageData
to get the image from an HTML canvas.
<!doctype html>
<html>
<head>
<title>Apviz - Demo T-Shirt</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
#my-image-input { position: absolute; top: 10px; left: 10px; }
</style>
</head>
<body>
<!-- Container for the Apviz 3D viewer. -->
<div id="apviz-3d-viewer" style="width: 100%; height:100vh"></div>
<!-- Your image input button. -->
<input id="my-image-input" type="file" accept=".png,.jpg,.jpeg,.bmp,.gif,.ico,.svg">
<!-- Your custom initialization script. -->
<script>
async function apvizShowcaseReady(showcase) {
// Initialize a 3D viewer and bind it to the div.
const viewer = await showcase.createViewer({
divId : "apviz-3d-viewer"
});
// Initial configuration that MUST explicitly include ALL the fields.
// This will start displaying 3D.
await viewer.update({
fields : {
"Shirt" : "Fabric Blue",
"Chest" : "Fabric White"
}
});
// File input event handler.
document.querySelector("#my-image-input").addEventListener("change", e => {
viewer.update({
markingZones : {
// Targets a specific marking zone via it's key.
"ChestZone" : {
image : {
color : { source: e.target.files[0] }
}
}
}
})
});
}
</script>
<!-- Non-blocking async script that will call "apvizShowcaseReady" once fully loaded. -->
<script
src="https://public.apviz.io/showcases/U0hPVzo5cHBEUDBtaEFM/releases/UkVMUzp0ajVPT052VDRS/main.js"
integrity="sha384-iYZkL8dCFSTNH/pf1FbH2vDpPfolzRbqwtqe+0m89NyojS4gGKI7mreAi5oVgZJp"
defer
crossorigin="anonymous"
data-apviz-callback="apvizShowcaseReady"
></script>
</body>
</html>
The provided image is displayed with the following rules:
- Image is stretched within the underlying marking zone according to the provided brick UV mapping.
- RGB(A) are blended in multiply mode with the underlying marking zone material.
More real world samples to better undertand how the marking surface mixes with the image:
ℹ️ To remove the marking image, you can update the viewer with the marking zone key set to null
.
🔐 Note that Apviz does not store the provided images on any server by itself.
Marking text
This realistic engraving simulation feature is useful if you have text engraving, stamping, or embossing available for your products whether it is for jewelry, watches, leather goods, shoes, etc.
You can dynamically display a text (with optional embossing/debossing relief) on a predefined marking zone using Viewer.update
with the markingZones
parameter.
The text you want to set must be nested within the markingZones
parameter object using the marking zone key you want to target. If you need to update multiple zones at the same time, you can set multiple marking zone keys.
The text
object allows passing extra formatting properties:
Property | Description | ||||
---|---|---|---|---|---|
text.value |
Single line text from 0 to 128 characters.
Note that new line \n and carriage return \r characters are not supported.
|
||||
text.fontkey |
Unique font key identifier as defined during showcase setup.
Font resources are automatically segmented so that the browser only needs to download font subsets of unicode ranges that are currently used in the text. This is particularly relevant with large fonts (Japanese, Chinese, etc.) in order to save bandwidth. |
||||
text.heightMode |
How text height is determined:
|
||||
text.horizontalAlignment |
"left" , "center" or "right" horizontal alignment.
|
||||
text.verticalAlignment |
"top" , "middle" or "bottom" vertical alignment.
|
You can also provide your marking zone key with a text.relief
object to handle embossing and debossing (e.g. for engraving, stamping, flocking, etc.):
Property | Description | ||||
---|---|---|---|---|---|
text.relief.direction |
"up" (embossing) or "down" (debossing) direction.
|
||||
text.relief.depth |
Embossing/debossing depth in meters. Value must be greater than 0 .
|
||||
text.relief.angle |
Slope value in degrees (bevel). Value must be between 1 and 90 inclusive.
|
<!doctype html>
<html>
<head>
<title>Apviz - Ring Engraving</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
#my-text-input { position: absolute; top: 10px; left: 10px; }
</style>
</head>
<body>
<!-- Container for the Apviz 3D viewer. -->
<div id="apviz-3d-viewer" style="width: 100%; height:100vh"></div>
<!-- Your text input. -->
<input id="my-text-input" type="text">
<!-- Your custom initialization script. -->
<script>
async function apvizShowcaseReady(showcase) {
// Initialize a 3D viewer and bind it to the div.
const viewer = await showcase.createViewer({
divId : "apviz-3d-viewer"
});
// Initial configuration that MUST explicitly include ALL the fields.
// This will start displaying 3D.
await viewer.update({
fields : {
"Gem" : "Diamond",
"Ring" : "Gold Pink"
}
});
// Text input event handler.
document.querySelector("#my-text-input").addEventListener("input", e => {
viewer.update({
markingZones : {
// Targets a specific marking zone via it's key.
"Engraving" : {
text : {
value : e.target.value,
fontKey : "Arial",
heightMode : "characters", // or "font"
horizontalAlignment : "center", // "left" or "right"
verticalAlignment : "middle", // "top", or "bottom"
relief : {
direction : "down", // or "up"
depth : 0.000002, // In meters.
angle : 80 // Between 1° and 90°.
}
}
}
}
})
});
}
</script>
<!-- Non-blocking async script that will call "apvizShowcaseReady" once fully loaded. -->
<script
src="https://public.apviz.io/showcases/U0hPVzo1ajlKbGQxOFpN/releases/UkVMUzpIejE1ME5UMk1w/main.js"
integrity="sha384-gsXDSToM7fE7cZsncUHyovYQwg2M+fi1S7LIlYStw1M1NhYL7E3eufm+TizwbO++"
defer
crossorigin="anonymous"
data-apviz-callback="apvizShowcaseReady"
></script>
</body>
</html>
The provided text is displayed with the following rules:
- Text takes as much height as possible.
- Text height decreases to fit in width.
- Texts is displayed using the material associated with the marking zone.
ℹ️ To remove the marking text, you can update the viewer with the marking zone key either set to null
or with an empty text.value
string.
Camera update
Your Apviz release starts with the camera you have initially set up in your 3D editor. However, you have the flexibility to modify the camera programmatically on-the-fly. This feature is particularly useful for directing attention to specific areas of your product, such as when employing the marking feature (see marking text or marking image for more details).
The Apviz orbital camera always points towards a given target position and can be rotated around it with touch screen and mouse.
The camera's location in relation to its target (the center of rotation) is determined by three key parameters: yaw, pitch, and distance:
You can pass a camera object to the update
function at any time like that:
await viewer.update({
camera : {
type : "ORBITAL_SMART",
focalLength : 80,
yaw : 0,
pitch : 0
}
});
The camera object is described by the following properties:
Property | Description | ||||||
---|---|---|---|---|---|---|---|
type
|
Camera type as a string:
|
||||||
focalLength
|
Lens focal length as an integer ranging between 10 mm (inclusive) and 1000 mm (inclusive). | ||||||
yaw
|
Initial longitudinal rotation of the camera around Y axis with the target as the center of rotation. The value is an angle in degrees ranging between 0° (inclusive) and 359.9° (inclusive), with 1 decimal precision. Rotation is counterclockwise. |
||||||
pitch
|
Initial latitudinal rotation of the camera around Z axis with the target as the center of rotation. The value is an angle in degrees ranging between -180° (inclusive) and 179.9° (inclusive), with 1 decimal precision. Positive rotation is counterclockwise. |
||||||
distance
|
Initial distance from camera to target in meters up to 5 decimal precision.
Depending on your camera
|
||||||
target
|
Only when using Target position object with the following properties:
|
If you wish to prevent viewing certain areas of your product that may be unsuitable, you can configure camera restrictions with the following optional properties:
Property | Description | ||||
---|---|---|---|---|---|
yawRestriction
|
Optional yaw restriction object with the following properties:
Note that initial |
||||
pitchRestriction
|
Optional pitch restriction object with the following properties:
Note that initial |
||||
distanceRestrictionMin
|
Optional minimum distance from camera to target in meters up to 5 decimal precision. A value equal to |
||||
distanceRestrictionMax
|
Optional maximum distance from camera to target in meters up to 5 decimal precision. A value equal to |
Example:
<!doctype html>
<html>
<head>
<title>Apviz - Camera switch</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
#my-camera-menu { position: absolute; top: 10px; left: 10px; }
</style>
</head>
<body>
<!-- Container for the Apviz 3D viewer. -->
<div id="apviz-3d-viewer" style="width: 100%; height:100vh"></div>
<!-- Your camera radio buttons. -->
<div id="my-camera-menu">
<input type="radio" name="camera" id="marking" value="marking" />
<label for="marking">Engraving zone</label>
<input type="radio" name="camera" id="gem" value="gem" />
<label for="gem">Gem</label>
<input type="radio" name="camera" id="ring" value="ring" />
<label for="ring">Whole ring</label>
</div>
<!-- Your custom initialization script. -->
<script>
async function apvizShowcaseReady(showcase) {
// Initialize a 3D viewer and bind it to the div.
const viewer = await showcase.createViewer({
divId : "apviz-3d-viewer"
});
// Initial configuration that MUST explicitly include ALL the fields.
// This will start displaying 3D.
await viewer.update({
fields : {
"Gem" : "Diamond",
"Ring" : "Gold Pink"
}
});
// Handle camera update.
document.querySelector("#marking").addEventListener("input", async () => {
await viewer.update({
camera : {
type: "ORBITAL_FIXED_TARGET",
focalLength: 800,
yaw: 110,
yawRestriction: { center: 110, size: 60 },
pitch: -80,
pitchRestriction: { center: -80, size: 40 },
distance: 0.4,
distanceRestrictionMin: 0.3,
distanceRestrictionMax: 0.5,
target: { x: 0, y: -0.01, z: 0 }
}
});
});
document.querySelector("#gem").addEventListener("input", async () => {
await viewer.update({
camera : {
type: "ORBITAL_FIXED_TARGET",
focalLength: 800,
yaw: 110,
pitch: -80,
pitchRestriction: { center: -30, size: 100 },
distance: 0.3,
distanceRestrictionMin: 0.2,
distanceRestrictionMax: 0.9,
target: { x: 0, y: 0.01, z: 0 }
}
});
});
document.querySelector("#ring").addEventListener("input", async () => {
await viewer.update({
camera : {
type: "ORBITAL_SMART",
focalLength: 50,
yaw: 90,
pitch: 0,
distanceRestrictionMin: 0.1
}
});
});
}
</script>
<!-- Non-blocking async script that will call "apvizShowcaseReady" once fully loaded. -->
<script
src="https://public.apviz.io/showcases/U0hPVzo1ajlKbGQxOFpN/releases/UkVMUzpIejE1ME5UMk1w/main.js"
integrity="sha384-gsXDSToM7fE7cZsncUHyovYQwg2M+fi1S7LIlYStw1M1NhYL7E3eufm+TizwbO++"
defer
crossorigin="anonymous"
data-apviz-callback="apvizShowcaseReady"
></script>
</body>
</html>
ℹ️ You can pass multiple properties simultaneously to the update
function, such as a field value, a marking zone, and a new camera state. The promise will be resolved once everything has been updated.
Get current camera state
The getCamera
method allows capturing the current camera state so that it can be restored later.
// Get current camera state:
const result = await viewer.getCamera();
// The nested camera object can be passed as-is back to the `update` function:
await viewer.update({
camera : result.camera
});
ℹ️ To capture the initial camera state, call the getCamera
method before making any update
calls.
Progress events
You can use Viewer.addUpdatingListener
to subscribe to update progress events. This is specially useful to display a progress bar:
async function apvizShowcaseReady(showcase) {
// Initialize a 3D viewer and bind it to the div.
const viewer = await showcase.createViewer({
divId : "apviz-3d-viewer"
});
// Handles the progress bar.
const myLoader = document.getElementById("myLoader");
await viewer.addUpdatingListener(progress => {
myLoader.innerHTML = `${progress} %`;
myLoader.style.display = progress === 100 ? "none" : "block";
});
// Set initial configuration.
// This will display 3D elements accordingly.
await viewer.update({
fields : { ... }
});
}
The progress event is specially designed not to bloat your UI. Progress event only triggers:
- When appropriate (HTTP requests > 500ms)
- With a minimum 500ms resolution
You can unsubscribe your callback from update events using Viewer.removeUpdatingListener
.
ℹ️ Note that if you only need to do things before/after 3D has been loaded (without progress), you only have to add asynchronous code before or after Viewer.update
.
Dispose
One important aspect in order to improve performance and avoid memory leaks in your application is the disposal of unused 3D viewer. Whenever you create an instance of a 3D viewer, you allocate a certain amount of GPU memory that is necessary for rendering. It's important to highlight that GPU memory is not released automatically. Instead, your application has to use Viewer.dispose()
in order to free such resources.
await viewer.dispose();
This is specially useful if you integrate Apviz within a Single-page application (SPA) or if you show/hide 3D multiple times within the same HTML page.
If you use a Multiple-page application (MPA) and the 3D viewer is only showed once, you can skip disposal.
ℹ️ Note that Viewer.dispose()
will also unbind the 3D viewer from your div
making it possibly available for another 3D viewer.
Get fields
If you need to get all configurable field values at runtime you can call the static getFields
method:
async function apvizShowcaseReady(showcase) {
// getFields returns an object with the following structure:
// {
// fields : [
// { key: "carat", values: [{ value: "0.5ct" }, { value: "1ct" }] },
// { key: "gem", values: [{ value: "diamond" }, { value: "ruby" }] },
// { key: "gold", values: [{ value: "yellow" }, { value: "white" }] }
// ]
// }
const { fields } = await showcase.getFields();
}
ℹ️ This method is intended to ease debugging or fast prototyping. Keep in mind that Apviz is not designed to handle human readable UI menu items.
Get marking zones
If you need to get all configurable marking zones at runtime you can call the static getMarkingZones
method:
async function apvizShowcaseReady(showcase) {
// Returns an object with the following structure:
// {
// markingZones : [
// { key: "Engraving", fonts: [{ key: "Arial" }, { key: "EnglischeSchTDemBol" }] }
// ]
// }
const { markingZones } = await showcase.getMarkingZones();
}
ℹ️ This method is intended to ease debugging or fast prototyping. Keep in mind that Apviz is not designed to handle human readable UI menu items.
Content Security Policy
Content Security Policies are delivered as a header to your users' browser by your web-server and they are used to declare which dynamic resources are allowed to load on your page.
For many websites, this is often as straightforward as declaring that only scripts/styles from your own domain and that of any tools that you are using is allowed, but this can become more involved when complex setups are in play.
If you identify CSP errors on your site, you will need to work with your development team or hosting provider to adjust your CSP settings by adding the following rules:
connect-src https://public.apviz.io;
default-src https://public.apviz.io;
img-src https://public.apviz.io blob:;
script-src https://public.apviz.io;
Compatibility
Apviz 3D configurator is standard HTML and JavaScript making it compatible with all CMS and e-commerce platforms like:
- Sitecore
- Salesforce
- Adobe Commerce (Magento)
- Shopify
- WooCommerce
- Prestashop
- Etsy
- Etc.
Apviz allows importing 3D bricks from an OBJ file which is supported by a wide variety of 3D/CAD softwares. We have strong support and experience for the following ones:
- Blender 3D software
- Rhinoceros 3D (aka Rhino) 3D CAD software
- Autodesk 3ds Max 3D software
- Gemvision MatrixGold 3D CAD software for jewellery design
- 3Design 3D CAS software for jewellery design
- CLO 3D garment design and simulation software
- Pixologic ZBrush 3D software
- Trimble SketchUp 3D software design
- Dassault Catia 3D engineering software
- And many more...
More features
This was just a quick overview of how to integrate a basic Apviz 3D configurator to a website. But we have many more features available like:
- Multi viewpoint
- Virtual try-on (augmented reality)
- Batch packshot
- Etc.
More documentation is avaible on studio.apviz.io.
Start now!
Request a free access and start building and integrating your own 3D configurators on your website today!