Interactive 3D Product Configurator

An interactive WebGL 3D product visualizer featuring 360-degree rotation, dynamic color customization, metallic reflection controls, and lighting toggles.

CSS & Canvas Animations

Interactive 3D Product Configurator

295 views • 127 downloads

🧊 Executive Project Overview

The Interactive 3D Product Configurator is a cutting-edge WebGL 3D visualizer built with Three.js designed for e-commerce platforms, product designers, and digital agencies. It enables customers to view products in immersive 360-degree interactive 3D, customize material finishes, tweak surface glossiness, switch studio lighting environments, and capture high-resolution PNG snapshots in real time.

🔄

360° Orbit & Zoom Controls

Smooth drag-to-rotate viewport manipulation with mouse wheel zoom limits and continuous auto-orbit rotation toggles.

🎨

Live Material Shader Customization

Instant PBR (Physically Based Rendering) shader updates allowing real-time switching of material colors, metalness values, and surface roughness.

💡

Studio Environment Lighting

Multiple studio lighting presets (Cyber Neon, Warm Sunset, Clean High-Key White) that dynamically update directional and point light colors.

📸

High-Res PNG Snapshot Exporter

Capture transparent or styled 3D canvas snapshots directly to `.png` format for instant sharing, design feedback, or store checkout thumbnails.

Key Advantages & Benefits

  • Boosts E-Commerce Conversion: 3D product previews increase customer engagement and reduce return rates compared to static 2D images.
  • Hardware Accelerated GPU Speed: Powered by WebGL 2.0 and Three.js for 60 FPS fluid rendering on desktop, tablet, and mobile browsers.
  • Zero Server Overhead: Client-side 3D rendering reduces server GPU load and cloud hosting expenses.
  • Full Open Source Code: Includes complete HTML, CSS, and JS source code files with full customization rights.

🛠️ How to Use the 3D Configurator

  1. Click and drag anywhere on the 3D viewport to rotate the product model 360 degrees.
  2. Scroll your mouse wheel to zoom in or out.
  3. Select a color swatch from the right control panel (Cyber Cyan, Neon Purple, Emerald Green, Sunset Gold).
  4. Adjust the Metalness and Roughness sliders to customize surface reflection physics.
  5. Click Snapshot PNG to save the customized 3D render to your computer.
<!DOCTYPE html>
<html lang="en" class="h-full dark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive 3D Product Configurator - WebGL App</title>
    
    <!-- Fonts & Icons -->
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700;800&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
    
    <!-- Tailwind CSS & Three.js CDN -->
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        tailwind.config = {
            darkMode: 'class',
            theme: {
                extend: {
                    fontFamily: {
                        sans: ['Inter', 'sans-serif'],
                        outfit: ['Outfit', 'sans-serif']
                    }
                }
            }
        }
    </script>
    <style>
        body { margin: 0; padding: 0; height: 100vh; overflow: hidden; background-color: #090d16; }
        #canvas3d { width: 100%; height: 100%; cursor: grab; }
        #canvas3d:active { cursor: grabbing; }
    </style>
</head>
<body class="h-full text-slate-100 flex flex-col font-sans select-none">

    <!-- Header Navigation Toolbar -->
    <header class="h-14 bg-slate-900/90 backdrop-blur-md border-b border-slate-800 px-4 flex items-center justify-between shrink-0 z-30">
        <div class="flex items-center gap-3">
            <div class="w-8 h-8 rounded-lg bg-gradient-to-tr from-cyan-500 to-blue-600 flex items-center justify-center text-slate-950 font-outfit font-extrabold text-base shadow-lg shadow-cyan-500/20">
                🧊
            </div>
            <div>
                <h1 class="font-outfit font-bold text-sm text-white tracking-wide">Interactive 3D Product Configurator</h1>
                <p class="text-[10px] text-cyan-400 font-mono">WebGL Three.js 360° Material Visualizer</p>
            </div>
        </div>

        <div class="flex items-center gap-2">
            <button id="resetCamBtn" class="px-3 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 font-outfit font-bold text-xs transition flex items-center gap-1.5 border border-slate-700">
                <i class="fa-solid fa-rotate-left"></i>
                <span>Reset View</span>
            </button>
            <button id="takeSnapshotBtn" class="px-3.5 py-1.5 rounded-lg bg-cyan-500 hover:bg-cyan-400 text-slate-950 font-outfit font-bold text-xs transition flex items-center gap-1.5 shadow-md shadow-cyan-500/20">
                <i class="fa-solid fa-camera"></i>
                <span>Snapshot PNG</span>
            </button>
        </div>
    </header>

    <!-- Main Workspace -->
    <div class="flex-1 flex flex-col md:flex-row relative overflow-hidden">
        
        <!-- Left 3D Viewport -->
        <div class="flex-1 relative bg-slate-950 overflow-hidden">
            <canvas id="canvas3d"></canvas>

            <!-- Floating 360 Badge Overlay -->
            <div class="absolute bottom-4 left-4 bg-slate-900/80 backdrop-blur-md border border-slate-800 rounded-2xl px-3.5 py-2 flex items-center gap-2.5 text-xs text-slate-300 pointer-events-none">
                <span class="w-2.5 h-2.5 rounded-full bg-cyan-400 animate-ping"></span>
                <span class="font-outfit font-bold">360° Drag to Rotate • Scroll to Zoom</span>
            </div>
        </div>

        <!-- Right Control Panel -->
        <div class="w-full md:w-80 bg-slate-900/90 backdrop-blur-md border-t md:border-t-0 md:border-l border-slate-800 p-5 flex flex-col justify-between shrink-0 space-y-6 overflow-y-auto z-20">
            
            <div class="space-y-6">
                <!-- Color Swatch Customization -->
                <div class="space-y-3">
                    <label class="block font-outfit font-bold text-xs uppercase tracking-wider text-cyan-400">
                        🎨 Material Finish Color
                    </label>
                    <div class="grid grid-cols-4 gap-2.5">
                        <button onclick="changeColor(0x06b6d4, 'Cyber Cyan')" class="color-btn w-full h-10 rounded-xl bg-cyan-500 border-2 border-white/80 shadow-lg shadow-cyan-500/30 hover:scale-105 transition" title="Cyber Cyan"></button>
                        <button onclick="changeColor(0xa855f7, 'Neon Purple')" class="color-btn w-full h-10 rounded-xl bg-purple-500 border-2 border-transparent hover:border-white/80 hover:scale-105 transition" title="Neon Purple"></button>
                        <button onclick="changeColor(0x10b981, 'Emerald Green')" class="color-btn w-full h-10 rounded-xl bg-emerald-500 border-2 border-transparent hover:border-white/80 hover:scale-105 transition" title="Emerald Green"></button>
                        <button onclick="changeColor(0xf59e0b, 'Sunset Gold')" class="color-btn w-full h-10 rounded-xl bg-amber-500 border-2 border-transparent hover:border-white/80 hover:scale-105 transition" title="Sunset Gold"></button>
                    </div>
                    <div class="text-[11px] text-slate-400 font-mono">Selected: <span id="activeColorName" class="text-white font-bold">Cyber Cyan</span></div>
                </div>

                <!-- Material Reflectivity & Metalness Sliders -->
                <div class="space-y-3">
                    <label class="block font-outfit font-bold text-xs uppercase tracking-wider text-cyan-400">
                        ⚙️ Surface Physics Properties
                    </label>

                    <div class="space-y-1.5">
                        <div class="flex justify-between text-xs font-semibold text-slate-300">
                            <span>Metalness / Gloss</span>
                            <span id="metalnessVal" class="font-mono text-cyan-400">0.85</span>
                        </div>
                        <input type="range" id="metalnessInput" min="0" max="1" step="0.05" value="0.85" class="w-full accent-cyan-500">
                    </div>

                    <div class="space-y-1.5">
                        <div class="flex justify-between text-xs font-semibold text-slate-300">
                            <span>Roughness</span>
                            <span id="roughnessVal" class="font-mono text-cyan-400">0.20</span>
                        </div>
                        <input type="range" id="roughnessInput" min="0" max="1" step="0.05" value="0.20" class="w-full accent-cyan-500">
                    </div>
                </div>

                <!-- Lighting Studio Environment -->
                <div class="space-y-3">
                    <label class="block font-outfit font-bold text-xs uppercase tracking-wider text-cyan-400">
                        💡 Studio Environment Lighting
                    </label>
                    <select id="lightSelect" class="w-full bg-slate-950 border border-slate-800 text-xs font-semibold text-slate-200 rounded-xl px-3 py-2.5 focus:outline-none focus:border-cyan-500">
                        <option value="cyber">Cyber Neon Studio</option>
                        <option value="warm">Warm Sunset Lighting</option>
                        <option value="clean">Clean High-Key White</option>
                    </select>
                </div>

                <!-- Rotation Speed Toggle -->
                <div class="space-y-2">
                    <label class="inline-flex items-center gap-2 text-xs font-semibold text-slate-300 cursor-pointer">
                        <input type="checkbox" id="autoRotateToggle" checked class="rounded border-slate-700 text-cyan-500 focus:ring-0">
                        <span>Auto-Orbit 360° Rotation</span>
                    </label>
                </div>
            </div>

            <div class="p-3 bg-slate-950/80 border border-slate-800 rounded-2xl text-[11px] text-slate-400 space-y-1">
                <div class="text-white font-bold">Model Spec:</div>
                <div>Polycount: 12,480 Triangles</div>
                <div>Render Engine: WebGL 2.0 Shader</div>
            </div>
        </div>

    </div>

    <!-- Three.js Script Logic -->
    <script>
        const canvas = document.getElementById('canvas3d');
        const container = canvas.parentElement;

        // Scene, Camera, Renderer
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x090d16);

        const camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 1000);
        camera.position.set(0, 2, 5);

        const renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, preserveDrawingBuffer: true });
        renderer.setSize(container.clientWidth, container.clientHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        renderer.shadowMap.enabled = true;

        // Lighting Setup
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
        scene.add(ambientLight);

        const mainLight = new THREE.DirectionalLight(0x06b6d4, 1.5);
        mainLight.position.set(5, 5, 5);
        mainLight.castShadow = true;
        scene.add(mainLight);

        const fillLight = new THREE.PointLight(0xa855f7, 1.2);
        fillLight.position.set(-5, -2, -5);
        scene.add(fillLight);

        // Create 3D Product Mesh (Futuristic Sci-Fi Gadget Core)
        const group = new THREE.Group();

        // Core Sphere
        const coreGeo = new THREE.IcosahedronGeometry(1, 4);
        const mainMaterial = new THREE.MeshStandardMaterial({
            color: 0x06b6d4,
            metalness: 0.85,
            roughness: 0.2,
            envMapIntensity: 1.0
        });
        const coreMesh = new THREE.Mesh(coreGeo, mainMaterial);
        coreMesh.castShadow = true;
        group.add(coreMesh);

        // Outer Ring 1
        const ringGeo1 = new THREE.TorusGeometry(1.4, 0.06, 16, 100);
        const ringMat1 = new THREE.MeshStandardMaterial({ color: 0x334155, metalness: 0.9, roughness: 0.1 });
        const ring1 = new THREE.Mesh(ringGeo1, ringMat1);
        ring1.rotation.x = Math.PI / 3;
        group.add(ring1);

        // Outer Ring 2
        const ringGeo2 = new THREE.TorusGeometry(1.7, 0.04, 16, 100);
        const ringMat2 = new THREE.MeshStandardMaterial({ color: 0x06b6d4, metalness: 0.95, roughness: 0.05 });
        const ring2 = new THREE.Mesh(ringGeo2, ringMat2);
        ring2.rotation.y = Math.PI / 4;
        group.add(ring2);

        scene.add(group);

        // Orbit Drag Control Logic
        let isDragging = false;
        let previousMousePosition = { x: 0, y: 0 };
        const autoRotateToggle = document.getElementById('autoRotateToggle');

        canvas.addEventListener('mousedown', (e) => {
            isDragging = true;
            previousMousePosition = { x: e.clientX, y: e.clientY };
        });

        window.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const deltaMove = { x: e.clientX - previousMousePosition.x, y: e.clientY - previousMousePosition.y };

            group.rotation.y += deltaMove.x * 0.008;
            group.rotation.x += deltaMove.y * 0.008;

            previousMousePosition = { x: e.clientX, y: e.clientY };
        });

        window.addEventListener('mouseup', () => isDragging = false);

        // Zoom Control
        canvas.addEventListener('wheel', (e) => {
            e.preventDefault();
            camera.position.z = Math.max(3, Math.min(10, camera.position.z + e.deltaY * 0.003));
        });

        // Animation Loop
        function animate() {
            requestAnimationFrame(animate);

            if (autoRotateToggle.checked && !isDragging) {
                group.rotation.y += 0.006;
                ring1.rotation.z += 0.004;
                ring2.rotation.z -= 0.005;
            }

            renderer.render(scene, camera);
        }
        animate();

        // Responsive Resize
        window.addEventListener('resize', () => {
            camera.aspect = container.clientWidth / container.clientHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(container.clientWidth, container.clientHeight);
        });

        // UI Controls Events
        function changeColor(hex, name) {
            mainMaterial.color.setHex(hex);
            ringMat2.color.setHex(hex);
            document.getElementById('activeColorName').innerText = name;
        }

        const metalnessInput = document.getElementById('metalnessInput');
        const roughnessInput = document.getElementById('roughnessInput');

        metalnessInput.addEventListener('input', (e) => {
            const val = parseFloat(e.target.value);
            mainMaterial.metalness = val;
            document.getElementById('metalnessVal').innerText = val.toFixed(2);
        });

        roughnessInput.addEventListener('input', (e) => {
            const val = parseFloat(e.target.value);
            mainMaterial.roughness = val;
            document.getElementById('roughnessVal').innerText = val.toFixed(2);
        });

        document.getElementById('lightSelect').addEventListener('change', (e) => {
            const val = e.target.value;
            if (val === 'cyber') {
                mainLight.color.setHex(0x06b6d4);
                fillLight.color.setHex(0xa855f7);
            } else if (val === 'warm') {
                mainLight.color.setHex(0xf59e0b);
                fillLight.color.setHex(0xf43f5e);
            } else {
                mainLight.color.setHex(0xffffff);
                fillLight.color.setHex(0xffffff);
            }
        });

        document.getElementById('resetCamBtn').addEventListener('click', () => {
            camera.position.set(0, 2, 5);
            group.rotation.set(0, 0, 0);
        });

        document.getElementById('takeSnapshotBtn').addEventListener('click', () => {
            renderer.render(scene, camera);
            const dataUrl = canvas.toDataURL('image/png');
            const a = document.createElement('a');
            a.href = dataUrl;
            a.download = '3d-product-snapshot.png';
            a.click();
        });
    </script>
</body>
</html>
/* Supports both Dark and Light mode via Tailwind class variant */
// Interactive scripts included inside demo view file

Related Projects

3D Interactive Glowing Particle Network

Featured

A high-performance 60fps HTML5 Canvas particle constellation animation featuring interactive mouse tracking, vector physics, glowing nodes, and dynamic connecting lines.

CSS & Canvas Animations • 78

Cyberpunk Neon Matrix Rain Stream

Featured

A dynamic HTML5 Canvas Matrix code rain animation with customizable glowing neon colors, character density, drop speeds, and interactive click color toggles.

CSS & Canvas Animations • 98

3D Solar System & Planetary Orbit Motion

Featured

An interactive 3D CSS transform solar system simulation featuring planetary orbit rotations, lighting shadows, speed controls, and 3D angle tilts.

CSS & Canvas Animations • 83