3D configurator integration overview

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:
<!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.
Basic marking image samples

More real world samples to better undertand how the marking surface mixes with the image:

Real world marking image samples

ℹ️ 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:
  • "font": Static text height based on the maximum font height, regardless of the characters.
  • "characters": Dynamic text height that perfectly fits the characters bounding box.
"font" "characters"
Font height mode Characters height mode
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.
Down Up
Depth behaviour with a down direction Depth behaviour with an up direction
text.relief.angle Slope value in degrees (bevel). Value must be between 1 and 90 inclusive.
Down Up
Angle behaviour with a down direction Angle behaviour with an up direction
An example with a debossed text:
<!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.
Basic marking text samples

ℹ️ 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:

Apvid 3D engine orbital camera diagram

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:

"ORBITAL_FIXED_TARGET" The target position is fixed and must be explicitly defined. No matter what brick is currently visible or not.
"ORBITAL_SMART"

The target position and the distance are automatically defined depending on what brick are currently visible.

At each update when the bricks are changing:

  • Target is automatically re-positioned to the centroid of all bricks on the scene.
  • The distance also may adapt in order to always have all bricks visible (with a 10% tolerance with the previous state).

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 type:

  • "ORBITAL_FIXED_TARGET": The property is mandatory.
  • "ORBITAL_SMART": The property is optional. When omitted, the distance will be automatically determined in order to fit all bricks in the div.

target

Only when using "ORBITAL_FIXED_TARGET".

Target position object with the following properties:

x X coordinate in meters up to 5 decimal precision.
y Y coordinate in meters up to 5 decimal precision.
z Z coordinate in meters up to 5 decimal precision.

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:

center The central yaw angle around which the camera is free to orbit. The camera's yaw will be restricted within the range center - size / 2 (inclusive) to center + size / 2 (inclusive). The value is expressed in degrees ranging between 0° (inclusive) and 359.9° (inclusive), with 1 decimal precision.
size

The angular size that defines the allowed yaw range around the center yaw. The camera's yaw will be restricted within the range center - size / 2 (inclusive) to center + size / 2 (inclusive). The value is expressed in degrees ranging between 0° (inclusive) and 359.9° (inclusive), with 1 decimal precision.

A value of 0° will completely lock the yaw to the center position.

Note that initial yaw MUST respect the provided restriction.

pitchRestriction

Optional pitch restriction object with the following properties:

center The central pitch angle around which the camera is free to orbit. The camera's pitch will be restricted within the range center - size / 2 (inclusive) to center + size / 2 (inclusive). The value is expressed in degrees ranging between -180° (inclusive) and 179.9° (inclusive), with 1 decimal precision.
size

The angular size that defines the allowed pitch range around the center pitch. The camera's pitch will be restricted within the range center - size / 2 (inclusive) to center + size / 2 (inclusive). The value is expressed in degrees ranging between 0° (inclusive) and 359.9° (inclusive), with 1 decimal precision.

A value of 0° will completely lock the pitch to the center position.

Note that initial pitch MUST respect the provided restriction.

distanceRestrictionMin

Optional minimum distance from camera to target in meters up to 5 decimal precision.

A value equal to distanceRestrictionMax completely locks the distance.

distanceRestrictionMax

Optional maximum distance from camera to target in meters up to 5 decimal precision.

A value equal to distanceRestrictionMin completely locks the distance.

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:

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:

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!