webdevpuneet.com

GLB Product Color Configurator — Free Three.js glTF Paint Swatch Snippet

GLB Product Color Configurator · Media · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="gcc-wrap">
  <canvas id="gccCanvas"></canvas>
  <div class="gcc-hint">Drag to orbit · Ctrl/Cmd + scroll to zoom</div>
  <div class="gcc-zoom">
    <span class="gcc-zoom-label">+</span>
    <input type="range" id="gccZoom" class="gcc-zoom-slider" min="0" max="100" step="1" />
    <span class="gcc-zoom-label">&minus;</span>
  </div>
  <div class="gcc-panel">
    <div class="gcc-panel-title">Paint finish</div>
    <div class="gcc-swatches" id="gccSwatches">
      <button class="gcc-swatch active" style="--c:#e2412c" data-color="#e2412c" title="Racing Red"></button>
      <button class="gcc-swatch" style="--c:#2563eb" data-color="#2563eb" title="Cobalt Blue"></button>
      <button class="gcc-swatch" style="--c:#16a34a" data-color="#16a34a" title="Racetrack Green"></button>
      <button class="gcc-swatch" style="--c:#f5b400" data-color="#f5b400" title="Taxi Yellow"></button>
      <button class="gcc-swatch" style="--c:#eef2f6" data-color="#eef2f6" title="Pearl White"></button>
      <button class="gcc-swatch" style="--c:#111318" data-color="#111318" title="Matte Black"></button>
    </div>
    <div class="gcc-panel-title">Surface</div>
    <div class="gcc-finish-row">
      <button class="gcc-finish-btn active" data-metal="0.1" data-rough="0.75">Matte</button>
      <button class="gcc-finish-btn" data-metal="0.85" data-rough="0.2">Metallic</button>
      <button class="gcc-finish-btn" data-metal="0.05" data-rough="0.05">Gloss</button>
    </div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Real glTF binary model
Loaded via THREE.GLTFLoader from an actual .glb file, not a primitive shape.
Live paint swatches
Six colors set .color on every cloned mesh material instantly.
Independent finish presets
Matte, Metallic, and Gloss change metalness/roughness as a separate axis.
Per-instance material cloning
Materials are cloned on load so recoloring never mutates shared glTF materials.
Auto-fit model scale
A bounding-box measurement scales the model to a known height, never a guess.
Honest load-failure fallback
A logged error swaps in a placeholder box that stays fully paintable.
Slider + Ctrl/Cmd-scroll zoom
Zoom is an explicit, opt-in gesture, never a hijacked plain scroll wheel.
Studio-lit scene
Key/fill/rim lighting and a grounding disc for a real product-shot feel.

About this UI Snippet

GLB Product Color Configurator — Real glTF Geometry, Live Paint and Finish Swatches

Screenshot of the GLB Product Color Configurator snippet rendered live

Most "3D color picker" demos recolor a primitive sphere or cube. This snippet does the real thing: it loads a genuine .glb model with Three.js's GLTFLoader, then lets a visitor click through paint swatches and surface-finish presets that change the model's *actual* materials in real time, on the actual loaded geometry.

Cloning materials before touching them

A glTF scene graph frequently reuses one material instance across several mesh nodes. Setting .color on a shared material would recolor every mesh using it, which is rarely what a single-product configurator wants. The moment the model loads, this snippet walks its full node tree with car.traverse(), and for every mesh clones its material with node.material.clone() before collecting it into a paintMaterials array. Every swatch and finish button afterward only ever touches that per-instance clone.

Auto-fit rather than a guessed scale

Different .glb exports — and even different loader versions — can decode the same source asset's geometry at wildly different raw sizes. Rather than hardcoding a scale factor, the snippet measures the loaded model's bounding box with THREE.Box3, finds its largest dimension, and scales it to a known target height. The model is then repositioned so its lowest point rests exactly on the ground disc, regardless of how the source file was authored.

Color and finish are two independent controls

Clicking a paint swatch calls applyColor(), which sets .color on every cloned material. Clicking a finish preset (Matte, Metallic, Gloss) calls applyFinish(), which sets .metalness and .roughness instead — completely separate PBR properties. Because both act on the same live MeshStandardMaterial instances, any combination of the six colors and three finishes renders correctly with zero extra state to track.

A named, honest fallback if the model fails

If GLTFLoader can't fetch the .glb, the real error is logged and a simple box mesh takes its place — reusing the exact same paintMaterials array, so the swatches and finish buttons keep working on the placeholder instead of silently doing nothing.

Zoom is opt-in, not a hijacked scroll wheel

OrbitControls' built-in wheel-zoom is deliberately turned off (controls.enableZoom = false) so a plain scroll over the card always scrolls the page. Zoom is reimplemented as two explicit, opt-in gestures instead: a vertical slider next to the canvas, and Ctrl/Cmd + scroll wheel, both driving the same setZoomDistance() helper.

Customizing it

Swap MODEL_URL for any other Khronos sample-asset .glb, add more swatch colors, or extend applyFinish() with an emissive property for a glow-in-the-dark colorway. Pair it with three product viewer for the non-glTF version of the same recoloring idea, or GLB exploded view assembly toggle for a different way to interact with a loaded model.

Step by step

How to Use

  1. 1
    Add all three CDN scriptsthree.min.js, GLTFLoader.js, and OrbitControls.js — in that order.
  2. 2
    Paste HTML, CSS, and JSThe toy car loads in racing red on a matte finish.
  3. 3
    Drag on the canvasOrbit the camera freely to inspect the model from any angle.
  4. 4
    Click a paint swatchEvery mesh's cloned material recolors instantly, live.
  5. 5
    Click a finish presetMatte, Metallic, or Gloss changes metalness and roughness together.
  6. 6
    Use the slider or Ctrl/Cmd + scroll to zoomPlain scroll always scrolls the page; zoom is a separate, opt-in gesture.
  7. 7
    Swap the model URLPoint MODEL_URL at any other .glb to configure a different product.

Real-world uses

Common Use Cases

E-commerce product configurators
Let shoppers click through six paint colours on a real model, with every mesh material cloned so the shared glTF is never altered.
Automotive and vehicle showcases
Preview a vehicle in different finishes, with Matte, Metallic and Gloss presets adjusting metalness and roughness independently of colour.
Furniture and design previews
Swap the toy car for a chair or lamp, keeping the same swatch and finish controls for upholstery or coating options.
Merchandise previews
Show print-on-demand products in the buyer's chosen colour, and combine with the GLB exploded view toggle to show construction.
glTF material teaching
Learn per-instance material cloning, which is what prevents one recolour from changing every mesh that shares the original material.
Related: Profile Completeness Card — Weighted Progress with Next-Best-Action
See the Profile Completeness Card — Weighted Progress with Next-Best-Action for a related cards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

A glTF file frequently reuses one material instance across multiple mesh nodes to keep the file small. Setting .color directly on that shared material would recolor every mesh using it, which usually isn't the intent of a single-product color configurator. Cloning each mesh's material once, right after load, guarantees every swatch click only ever affects this specific model instance.

They control different PBR properties. Paint swatches set .color, the base tint of the surface. Finish presets set .metalness and .roughness together, which controls how that color reflects light — matte, brushed metal, or high gloss. Keeping them independent means any of the six colors can be combined with any of the three finishes without extra state tracking.

After loading, the snippet measures the model's actual bounding box with THREE.Box3, finds its largest dimension, and scales the whole model so that dimension matches a fixed target height. It then re-measures the scaled bounding box and shifts the model vertically so its lowest point sits exactly on the ground disc — a hardcoded scale number would break the moment a differently-exported .glb was swapped in.

The loader's error callback logs the real failure and creates a simple box mesh in its place, adding that placeholder's material to the same paintMaterials array the swatches and finish buttons already read from — so the configurator UI keeps working correctly even without the real model.

Set up the renderer, scene, GLTFLoader call, and OrbitControls inside a mount effect, storing paintMaterials and the loaded model in refs so the swatch and finish click handlers can reach current values. Call controls.dispose() and renderer.dispose() in the cleanup function to release the WebGL context and drag listeners on unmount.