Three JS and How to Get Started

Author: JJustis | Published: 2026-01-16 20:22:03
Article Image 1

🎮 Why three.js is Perfect for Web-Based 3D

three.js is a powerful JavaScript library that makes WebGL accessible. Unlike raw WebGL or game engines like Unity/Unreal:
  • No compilation needed - Runs directly in the browser
  • Tiny footprint - ~500KB vs multi-gigabyte game engines
  • Immediate visual feedback - See changes instantly
  • Perfect for prototypes & editors - Quick iteration cycle
  • Massive community & examples - Solutions for almost any 3D problem
In this tutorial, we'll build a complete 3D world editor with physics, object manipulation, and real-time interaction.

🚀 Working Example: Live 3D World Editor

Editor Controls

Add Objects
Selected Object: None
Physics & Environment
Instructions
  • Click: Select objects
  • Drag & Drop: Move selected object
  • Scroll: Zoom in/out
  • Right-click drag: Rotate camera
  • Middle-click drag: Pan camera

The Complete three.js Implementation

Step 1: Basic HTML Setup

Start with a simple HTML file and include three.js from a CDN:

<!DOCTYPE html>
<html>
<head>
    <title>three.js World Editor</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
    </style>
</head>
<body>
    <div id="canvasContainer"></div>
    
    <!-- Include three.js from CDN -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        // Our JavaScript code goes here
    </script>
</body>
</html>

Step 2: Core three.js Scene Setup

Here's the minimal three.js scene setup that powers our editor:

// ===== THREE.JS CORE SETUP =====
let scene, camera, renderer;
let objects = [];
let selectedObject = null;
let physicsEnabled = true;
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();

function init() {
    // 1. Create scene (the 3D world)
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x1a1a2e);
    
    // 2. Create camera (our view into the world)
    camera = new THREE.PerspectiveCamera(75, 
        window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.set(10, 10, 10);
    camera.lookAt(0, 0, 0);
    
    // 3. Create renderer (draws everything)
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.getElementById('canvasContainer').appendChild(renderer.domElement);
    
    // 4. Add lighting
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
    scene.add(ambientLight);
    
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(10, 20, 15);
    scene.add(directionalLight);
    
    // 5. Add a ground plane
    const groundGeometry = new THREE.PlaneGeometry(30, 30);
    const groundMaterial = new THREE.MeshLambertMaterial({ 
        color: 0x3a6b35, 
        side: THREE.DoubleSide 
    });
    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
    ground.rotation.x = Math.PI / 2;
    ground.position.y = -1;
    scene.add(ground);
    
    // 6. Add initial objects
    for(let i = 0; i < 5; i++) {
        addPrimitive('cube', i * 3 - 6, 2, 0);
    }
    
    // 7. Set up event listeners
    setupEventListeners();
    
    // 8. Start animation loop
    animate();
}

function animate() {
    requestAnimationFrame(animate);
    
    // Apply simple physics to bouncy objects
    if(physicsEnabled) {
        objects.forEach(obj => {
            if(obj.userData.bouncy) {
                obj.rotation.x += 0.01;
                obj.rotation.y += 0.005;
                
                // Simple bounce effect
                obj.position.y = Math.sin(Date.now() * 0.002 + obj.id) * 0.5 + 2;
            }
        });
    }
    
    renderer.render(scene, camera);
}

// Initialize everything when page loads
window.addEventListener('DOMContentLoaded', init);

Step 3: Object Creation Functions

These functions create different 3D primitives with random colors:

// ===== OBJECT CREATION =====
function createPrimitive(type, x, y, z) {
    let geometry, material;
    const color = new THREE.Color(Math.random(), Math.random(), Math.random());
    
    switch(type) {
        case 'cube':
            geometry = new THREE.BoxGeometry(1, 1, 1);
            break;
        case 'sphere':
            geometry = new THREE.SphereGeometry(0.5, 16, 16);
            break;
        case 'cone':
            geometry = new THREE.ConeGeometry(0.5, 1, 16);
            break;
        case 'cylinder':
            geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 16);
            break;
        case 'torus':
            geometry = new THREE.TorusGeometry(0.5, 0.2, 16, 32);
            break;
    }
    
    material = new THREE.MeshLambertMaterial({ 
        color: color,
        transparent: true,
        opacity: 0.9
    });
    
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(x, y, z);
    mesh.userData = { 
        type: type, 
        bouncy: Math.random() > 0.5,
        originalY: y
    };
    
    // Add wireframe for better visibility
    const wireframe = new THREE.LineSegments(
        new THREE.EdgesGeometry(geometry),
        new THREE.LineBasicMaterial({ color: 0x000000 })
    );
    mesh.add(wireframe);
    
    return mesh;
}

function addPrimitive(type, x, y, z) {
    const obj = createPrimitive(type, x, y, z);
    scene.add(obj);
    objects.push(obj);
    return obj;
}

// UI helper functions for the buttons
function addCube() { addPrimitive('cube', 0, 2, 0); }
function addSphere() { addPrimitive('sphere', 0, 2, 0); }
function addCone() { addPrimitive('cone', 0, 2, 0); }
function addCylinder() { addPrimitive('cylinder', 0, 2, 0); }
function addTorus() { addPrimitive('torus', 0, 2, 0); }

Step 4: Object Selection & Interaction

This is where we handle user interaction with 3D objects:

// ===== OBJECT INTERACTION =====
function setupEventListeners() {
    const canvas = renderer.domElement;
    let isDragging = false;
    let dragStart = new THREE.Vector2();
    
    // Click to select objects
    canvas.addEventListener('click', (event) => {
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        
        raycaster.setFromCamera(mouse, camera);
        const intersects = raycaster.intersectObjects(objects);
        
        if(intersects.length > 0) {
            selectObject(intersects[0].object);
        } else {
            deselectObject();
        }
    });
    
    // Drag to move objects
    canvas.addEventListener('mousedown', (event) => {
        if(event.button === 0 && selectedObject) { // Left click
            dragStart.set(event.clientX, event.clientY);
            isDragging = true;
        }
    });
    
    canvas.addEventListener('mousemove', (event) => {
        if(isDragging && selectedObject) {
            // Calculate drag distance
            const deltaX = (event.clientX - dragStart.x) * 0.02;
            const deltaY = -(event.clientY - dragStart.y) * 0.02;
            
            // Move object relative to camera orientation
            const forward = new THREE.Vector3();
            const right = new THREE.Vector3();
            camera.getWorldDirection(forward);
            right.crossVectors(camera.up, forward).normalize();
            
            selectedObject.position.add(
                right.multiplyScalar(deltaX)
            );
            selectedObject.position.add(
                camera.up.clone().multiplyScalar(deltaY)
            );
            
            // Keep object above ground
            selectedObject.position.y = Math.max(0.5, selectedObject.position.y);
            
            dragStart.set(event.clientX, event.clientY);
        }
    });
    
    canvas.addEventListener('mouseup', () => {
        isDragging = false;
    });
    
    // Camera controls with mouse
    canvas.addEventListener('contextmenu', (event) => event.preventDefault());
    
    let isRotating = false;
    let rotateStart = new THREE.Vector2();
    
    canvas.addEventListener('mousedown', (event) => {
        if(event.button === 2) { // Right click
            rotateStart.set(event.clientX, event.clientY);
            isRotating = true;
        }
    });
    
    canvas.addEventListener('mousemove', (event) => {
        if(isRotating) {
            const deltaX = (event.clientX - rotateStart.x) * 0.01;
            const deltaY = (event.clientY - rotateStart.y) * 0.01;
            
            // Rotate camera around center point
            camera.position.x = Math.cos(deltaX) * camera.position.x - Math.sin(deltaX) * camera.position.z;
            camera.position.z = Math.sin(deltaX) * camera.position.x + Math.cos(deltaX) * camera.position.z;
            
            camera.lookAt(0, 0, 0);
            rotateStart.set(event.clientX, event.clientY);
        }
    });
    
    canvas.addEventListener('mouseup', (event) => {
        if(event.button === 2) {
            isRotating = false;
        }
    });
    
    // Zoom with scroll wheel
    canvas.addEventListener('wheel', (event) => {
        camera.position.multiplyScalar(event.deltaY > 0 ? 1.1 : 0.9);
        camera.lookAt(0, 0, 0);
    });
    
    // Handle window resize
    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    });
}

function selectObject(obj) {
    // Deselect previous object
    if(selectedObject) {
        selectedObject.material.emissive.setHex(selectedObject.userData.originalEmissive || 0x000000);
    }
    
    // Select new object
    selectedObject = obj;
    selectedObject.userData.originalEmissive = selectedObject.material.emissive.getHex();
    selectedObject.material.emissive.setHex(0x888888);
    
    // Update UI
    document.getElementById('selectedObject').textContent = 
        selectedObject.userData.type.charAt(0).toUpperCase() + selectedObject.userData.type.slice(1);
    document.getElementById('objectControls').style.display = 'block';
}

function deselectObject() {
    if(selectedObject) {
        selectedObject.material.emissive.setHex(selectedObject.userData.originalEmissive || 0x000000);
        selectedObject = null;
    }
    
    document.getElementById('selectedObject').textContent = 'None';
    document.getElementById('objectControls').style.display = 'none';
}

Step 5: Editor Controls Implementation

Finally, the control functions for our UI buttons:

// ===== EDITOR CONTROLS =====
function deleteObject() {
    if(selectedObject) {
        const index = objects.indexOf(selectedObject);
        if(index > -1) {
            objects.splice(index, 1);
            scene.remove(selectedObject);
            deselectObject();
        }
    }
}

function duplicateObject() {
    if(selectedObject) {
        const newObj = selectedObject.clone();
        newObj.position.x += 1.5; // Offset from original
        newObj.position.z += 1.5;
        newObj.material = selectedObject.material.clone();
        newObj.userData = {...selectedObject.userData};
        scene.add(newObj);
        objects.push(newObj);
        selectObject(newObj);
    }
}

function changeColor() {
    if(selectedObject) {
        selectedObject.material.color.setRGB(Math.random(), Math.random(), Math.random());
    }
}

function makeBouncy() {
    if(selectedObject) {
        selectedObject.userData.bouncy = !selectedObject.userData.bouncy;
        alert(selectedObject.userData.bouncy ? 
            'Object is now bouncy!' : 'Object is no longer bouncy.');
    }
}

function togglePhysics() {
    physicsEnabled = !physicsEnabled;
    alert(physicsEnabled ? 'Physics enabled!' : 'Physics disabled!');
}

function resetWorld() {
    // Remove all objects
    objects.forEach(obj => scene.remove(obj));
    objects = [];
    
    // Add fresh objects
    for(let i = 0; i < 5; i++) {
        addPrimitive('cube', i * 3 - 6, 2, 0);
    }
    
    deselectObject();
}

function saveScene() {
    const sceneData = objects.map(obj => ({
        type: obj.userData.type,
        position: {x: obj.position.x, y: obj.position.y, z: obj.position.z},
        color: obj.material.color.getHex(),
        bouncy: obj.userData.bouncy
    }));
    
    console.log('Scene saved:', sceneData);
    alert('Scene data saved to browser console! Press F12 to view.');
}

Step 6: Deployment & Next Steps

To deploy this editor:

  1. Copy all the JavaScript code into one file
  2. Save it as editor.html
  3. Open it directly in any modern browser - no server needed!

Advanced features to add:

  • Add Cannon.js or Ammo.js for realistic physics
  • Implement undo/redo functionality
  • Add texture loading for custom materials
  • Implement export to GLTF/OBJ formats
  • Add multiple selection and group operations
  • Implement grid snapping for precise placement

✨ Why This Matters for Developers

This working example demonstrates why three.js is revolutionary for web development:

1. Speed to Prototype: We built a fully functional 3D editor in under 200 lines of core code. Try doing that in Unity or Unreal!

2. Zero Dependencies: Just one JavaScript file (three.js) and native browser APIs.

3. Instant Deployment: Runs on any device with a web browser - desktop, mobile, tablet.

4. Perfect for:
  • Architecture visualization tools
  • Product configurators
  • Educational simulations
  • Data visualization dashboards
  • Game level editors
  • VR/AR prototypes

The complete code above is fully functional - copy it, run it, and start building your own 3D applications today!