--- name: threejs-skills description: "Create 3D scenes, interactive experiences, and visual effects using Three.js. Use when user requests 3D graphics, WebGL experiences, 3D visualizations, animations, or interactive 3D elements." risk: safe source: "https://github.com/CloudAI-X/threejs-skills" date_added: "2026-02-27" --- # Three.js Skills Systematically create high-quality 3D scenes and interactive experiences using Three.js best practices. ## When to Use - Requests 3D visualizations or graphics ("create a 3D model", "show in 3D") - Wants interactive 3D experiences ("rotating cube", "explorable scene") - Needs WebGL or canvas-based rendering - Asks for animations, particles, or visual effects - Mentions Three.js, WebGL, or 3D rendering - Wants to visualize data in 3D space ## Core Setup Pattern ### 1. Essential Three.js Imports Always use the correct CDN version (r128): ```javascript import * as THREE from "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"; ``` **CRITICAL**: Do NOT use example imports like `THREE.OrbitControls` - they won't work on the CDN. ### 2. Scene Initialization Every Three.js artifact needs these core components: ```javascript // Scene - contains all 3D objects const scene = new THREE.Scene(); // Camera - defines viewing perspective const camera = new THREE.PerspectiveCamera( 75, // Field of view window.innerWidth / window.innerHeight, // Aspect ratio 0.1, // Near clipping plane 1000, // Far clipping plane ); camera.position.z = 5; // Renderer - draws the scene const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); ``` ### 3. Animation Loop Use requestAnimationFrame for smooth rendering: ```javascript function animate() { requestAnimationFrame(animate); // Update object transformations here mesh.rotation.x += 0.01; mesh.rotation.y += 0.01; renderer.render(scene, camera); } animate(); ``` ## Systematic Development Process ### 1. Define the Scene Start by identifying: - **What objects** need to be rendered - **Camera position** and field of view - **Lighting setup** required - **Interaction model** (static, rotating, user-controlled) ### 2. Build Geometry Choose appropriate geometry types: **Basic Shapes:** - `BoxGeometry` - cubes, rectangular prisms - `SphereGeometry` - spheres, planets - `CylinderGeometry` - cylinders, tubes - `PlaneGeometry` - flat surfaces, ground planes - `TorusGeometry` - donuts, rings **IMPORTANT**: Do NOT use `CapsuleGeometry` (introduced in r142, not available in r128) **Alternatives for capsules:** - Combine `CylinderGeometry` + 2 `SphereGeometry` - Use `SphereGeometry` with adjusted parameters - Create custom geometry with vertices ### 3. Apply Materials Choose materials based on visual needs: **Common Materials:** - `MeshBasicMaterial` - unlit, flat colors (no lighting needed) - `MeshStandardMaterial` - physically-based, realistic (needs lighting) - `MeshPhongMaterial` - shiny surfaces with specular highlights - `MeshLambertMaterial` - matte surfaces, diffuse reflection ```javascript const material = new THREE.MeshStandardMaterial({ color: 0x00ff00, metalness: 0.5, roughness: 0.5, }); ``` ### 4. Add Lighting **If using lit materials** (Standard, Phong, Lambert), add lights: ```javascript // Ambient light - general illumination const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); // Directional light - like sunlight const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 5, 5); scene.add(directionalLight); ``` **Skip lighting** if using `MeshBasicMaterial` - it's unlit by design. ### 5. Handle Responsiveness Always add window resize handling: ```javascript window.addEventListener("resize", () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); ``` ## Common Patterns ### Rotating Object ```javascript function animate() { requestAnimationFrame(animate); mesh.rotation.x += 0.01; mesh.rotation.y += 0.01; renderer.render(scene, camera); } ``` ### Custom Camera Controls (OrbitControls Alternative) Since `THREE.OrbitControls` isn't available on CDN, implement custom controls: ```javascript let isDragging = false; let previousMousePosition = { x: 0, y: 0 }; renderer.domElement.addEventListener("mousedown", () => { isDragging = true; }); renderer.domElement.addEventListener("mouseup", () => { isDragging = false; }); renderer.domElement.addEventListener("mousemove", (event) => { if (isDragging) { const deltaX = event.clientX - previousMousePosition.x; const deltaY = event.clientY - previousMousePosition.y; // Rotate camera around scene const rotationSpeed = 0.005; camera.position.x += deltaX * rotationSpeed; camera.position.y -= deltaY * rotationSpeed; camera.lookAt(scene.position); } previousMousePosition = { x: event.clientX, y: event.clientY }; }); // Zoom with mouse wheel renderer.domElement.addEventListener("wheel", (event) => { event.preventDefault(); camera.position.z += event.deltaY * 0.01; camera.position.z = Math.max(2, Math.min(20, camera.position.z)); // Clamp }); ``` ### Raycasting for Object Selection Detect mouse clicks and hovers on 3D objects: ```javascript const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); const clickableObjects = []; // Array of meshes that can be clicked // Update mouse position window.addEventListener("mousemove", (event) => { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; }); // Detect clicks window.addEventListener("click", () => { raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(clickableObjects); if (intersects.length > 0) { const clickedObject = intersects[0].object; // Handle click - change color, scale, etc. clickedObject.material.color.set(0xff0000); } }); // Hover effect in animation loop function animate() { requestAnimationFrame(animate); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(clickableObjects); // Reset all objects clickableObjects.forEach((obj) => { obj.scale.set(1, 1, 1); }); // Highlight hovered object if (intersects.length > 0) { intersects[0].object.scale.set(1.2, 1.2, 1.2); document.body.style.cursor = "pointer"; } else { document.body.style.cursor = "default"; } renderer.render(scene, camera); } ``` ### Particle System ```javascript const particlesGeometry = new THREE.BufferGeometry(); const particlesCount = 1000; const posArray = new Float32Array(particlesCount * 3); for (let i = 0; i < particlesCount * 3; i++) { posArray[i] = (Math.random() - 0.5) * 10; } particlesGeometry.setAttribute( "position", new THREE.BufferAttribute(posArray, 3), ); const particlesMaterial = new THREE.PointsMaterial({ size: 0.02, color: 0xffffff, }); const particlesMesh = new THREE.Points(particlesGeometry, particlesMaterial); scene.add(particlesMesh); ``` ### User Interaction (Mouse Movement) ```javascript let mouseX = 0; let mouseY = 0; document.addEventListener("mousemove", (event) => { mouseX = (event.clientX / window.innerWidth) * 2 - 1; mouseY = -(event.clientY / window.innerHeight) * 2 + 1; }); function animate() { requestAnimationFrame(animate); camera.position.x = mouseX * 2; camera.position.y = mouseY * 2; camera.lookAt(scene.position); renderer.render(scene, camera); } ``` ### Loading Textures ```javascript const textureLoader = new THREE.TextureLoader(); const texture = textureLoader.load("texture-url.jpg"); const material = new THREE.MeshStandardMaterial({ map: texture, }); ``` ## Best Practices ### Performance - **Reuse geometries and materials** when creating multiple similar objects - **Use `BufferGeometry`** for custom shapes (more efficient) - **Limit particle counts** to maintain 60fps (start with 1000-5000) - **Dispose of resources** when removing objects: ```javascript geometry.dispose(); material.dispose(); texture.dispose(); ``` ### Visual Quality - Always set `antialias: true` on renderer for smooth edges - Use appropriate camera FOV (45-75 degrees typical) - Position lights thoughtfully - avoid overlapping multiple bright lights - Add ambient + directional lighting for realistic scenes ### Code Organization - Initialize scene, camera, renderer at the top - Group related objects (e.g., all particles in one group) - Keep animation logic in the animate function - Separate object creation into functions for complex scenes ### Common Pitfalls to Avoid - ❌ Using `THREE.OrbitControls` - not available on CDN - ❌ Using `THREE.CapsuleGeometry` - requires r142+ - ❌ Forgetting to add objects to scene with `scene.add()` - ❌ Using lit materials without adding lights - ❌ Not handling window resize - ❌ Forgetting to call `renderer.render()` in animation loop ## Example Workflow User: "Create an interactive 3D sphere that responds to mouse movement" 1. **Setup**: Import Three.js (r128), create scene/camera/renderer 2. **Geometry**: Create `SphereGeometry(1, 32, 32)` for smooth sphere 3. **Material**: Use `MeshStandardMaterial` for realistic look 4. **Lighting**: Add ambient + directional lights 5. **Interaction**: Track mouse position, update camera 6. **Animation**: Rotate sphere, render continuously 7. **Responsive**: Add window resize handler 8. **Result**: Smooth, interactive 3D sphere ✓ ## Troubleshooting **Black screen / Nothing renders:** - Check if objects added to scene - Verify camera position isn't inside objects - Ensure renderer.render() is called - Add lights if using lit materials **Poor performance:** - Reduce particle count - Lower geometry detail (segments) - Reuse materials/geometries - Check browser console for errors **Objects not visible:** - Check object position vs camera position - Verify material has visible color/properties - Ensure camera far plane includes objects - Add lighting if needed ## Advanced Techniques ### Visual Polish for Portfolio-Grade Rendering **Shadows:** ```javascript // Enable shadows on renderer renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows // Light that casts shadows const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(5, 10, 5); directionalLight.castShadow = true; // Configure shadow quality directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50; scene.add(directionalLight); // Objects cast and receive shadows mesh.castShadow = true; mesh.receiveShadow = true; // Ground plane receives shadows const groundGeometry = new THREE.PlaneGeometry(20, 20); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 }); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground); ``` **Environment Maps & Reflections:** ```javascript // Create environment map from cubemap const loader = new THREE.CubeTextureLoader(); const envMap = loader.load([ "px.jpg", "nx.jpg", // positive x, negative x "py.jpg", "ny.jpg", // positive y, negative y "pz.jpg", "nz.jpg", // positive z, negative z ]); scene.environment = envMap; // Affects all PBR materials scene.background = envMap; // Optional: use as skybox // Or apply to specific materials const material = new THREE.MeshStandardMaterial({ metalness: 1.0, roughness: 0.1, envMap: envMap, }); ``` **Tone Mapping & Output Encoding:** ```javascript // Improve color accuracy and HDR rendering renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.0; renderer.outputEncoding = THREE.sRGBEncoding; // Makes colors more vibrant and realistic ``` **Fog for Depth:** ```javascript // Linear fog scene.fog = new THREE.Fog(0xcccccc, 10, 50); // color, near, far // Or exponential fog (more realistic) scene.fog = new THREE.FogExp2(0xcccccc, 0.02); // color, density ``` ### Custom Geometry from Vertices ```javascript const geometry = new THREE.BufferGeometry(); const vertices = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0]); geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); ``` ### Post-Processing Effects While advanced post-processing may not be available in r128 CDN, basic effects can be achieved with shaders and render targets. ### Group Objects ```javascript const group = new THREE.Group(); group.add(mesh1); group.add(mesh2); group.rotation.y = Math.PI / 4; scene.add(group); ``` ## Summary Three.js artifacts require systematic setup: 1. Import correct CDN version (r128) 2. Initialize scene, camera, renderer 3. Create geometry + material = mesh 4. Add lighting if using lit materials 5. Implement animation loop 6. Handle window resize 7. Avoid r128 incompatible features Follow these patterns for reliable, performant 3D experiences. ## Modern Three.js & Production Practices While this skill focuses on CDN-based Three.js (r128) for artifact compatibility, here's what you'd do in production environments: ### Modular Imports with Build Tools ```javascript // In production with npm/vite/webpack: import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer"; ``` **Benefits:** - Tree-shaking (smaller bundle sizes) - Access to full example library (OrbitControls, loaders, etc.) - Latest Three.js features (r150+) - TypeScript support ### Animation Libraries (GSAP Integration) ```javascript // Smooth timeline-based animations import gsap from "gsap"; // Instead of manual animation loops: gsap.to(mesh.position, { x: 5, duration: 2, ease: "power2.inOut", }); // Complex sequences: const timeline = gsap.timeline(); timeline .to(mesh.rotation, { y: Math.PI * 2, duration: 2 }) .to(mesh.scale, { x: 2, y: 2, z: 2, duration: 1 }, "-=1"); ``` **Why GSAP:** - Professional easing functions - Timeline control (pause, reverse, scrub) - Better than manual lerping for complex animations ### Scroll-Based Interactions ```javascript // Sync 3D animations with page scroll let scrollY = window.scrollY; window.addEventListener("scroll", () => { scrollY = window.scrollY; }); function animate() { requestAnimationFrame(animate); // Rotate based on scroll position mesh.rotation.y = scrollY * 0.001; // Move camera through scene camera.position.y = -(scrollY / window.innerHeight) * 10; renderer.render(scene, camera); } ``` **Advanced scroll libraries:** - ScrollTrigger (GSAP plugin) - Locomotive Scroll - Lenis smooth scroll ### Performance Optimization in Production ```javascript // Level of Detail (LOD) const lod = new THREE.LOD(); lod.addLevel(highDetailMesh, 0); // Close up lod.addLevel(mediumDetailMesh, 10); // Medium distance lod.addLevel(lowDetailMesh, 50); // Far away scene.add(lod); // Instanced meshes for many identical objects const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshStandardMaterial(); const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000); // Set transforms for each instance const matrix = new THREE.Matrix4(); for (let i = 0; i < 1000; i++) { matrix.setPosition( Math.random() * 100, Math.random() * 100, Math.random() * 100, ); instancedMesh.setMatrixAt(i, matrix); } ``` ### Modern Loading Patterns ```javascript // In production, load 3D models: import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; const loader = new GLTFLoader(); loader.load("model.gltf", (gltf) => { scene.add(gltf.scene); // Traverse and setup materials gltf.scene.traverse((child) => { if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; } }); }); ``` ### When to Use What **CDN Approach (Current Skill):** - Quick prototypes and demos - Educational content - Artifacts and embedded experiences - No build step required **Production Build Approach:** - Client projects and portfolios - Complex applications - Need latest features (r150+) - Performance-critical applications - Team collaboration with version control ### Recommended Production Stack ``` Three.js (latest) + Vite/Webpack ├── GSAP (animations) ├── React Three Fiber (optional - React integration) ├── Drei (helper components) ├── Leva (debug GUI) └── Post-processing effects ``` This skill provides CDN-compatible foundations. In production, you'd layer on these modern tools for professional results.