Skill v1.0.1
currentAutomated scan100/1002 files
version: "1.0.1" 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
Use ES module import maps for modern Three.js (r183+):
<script type="importmap">{"imports": {"three": "https://cdn.jsdelivr.net/npm/three@0.183.0/build/three.module.js","three/addons/": "https://cdn.jsdelivr.net/npm/three@0.183.0/examples/jsm/"}}</script><script type="module">import * as THREE from "three";import { OrbitControls } from "three/addons/controls/OrbitControls.js";</script>
For production with npm/vite/webpack:
import * as THREE from "three";import { OrbitControls } from "three/addons/controls/OrbitControls.js";
2. Scene Initialization
Every Three.js artifact needs these core components:
// Scene - contains all 3D objectsconst scene = new THREE.Scene();// Camera - defines viewing perspectiveconst camera = new THREE.PerspectiveCamera(75, // Field of viewwindow.innerWidth / window.innerHeight, // Aspect ratio0.1, // Near clipping plane1000, // Far clipping plane);camera.position.z = 5;// Renderer - draws the sceneconst renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);
3. Animation Loop
Use renderer.setAnimationLoop() (preferred) or requestAnimationFrame:
// Preferred: setAnimationLoop (handles WebXR compatibility)renderer.setAnimationLoop(() => {mesh.rotation.x += 0.01;mesh.rotation.y += 0.01;renderer.render(scene, camera);});// Alternative: manual requestAnimationFramefunction animate() {requestAnimationFrame(animate);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 prismsSphereGeometry- spheres, planetsCylinderGeometry- cylinders, tubesPlaneGeometry- flat surfaces, ground planesTorusGeometry- donuts, rings
CapsuleGeometry is available (stable since r142):
new THREE.CapsuleGeometry(0.5, 1, 4, 8); // radius, length, capSegments, radialSegments
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 highlightsMeshLambertMaterial- matte surfaces, diffuse reflection
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:
// Ambient light - general illuminationconst ambientLight = new THREE.AmbientLight(0xffffff, 0.5);scene.add(ambientLight);// Directional light - like sunlightconst 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:
window.addEventListener("resize", () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});
Common Patterns
Rotating Object
function animate() {requestAnimationFrame(animate);mesh.rotation.x += 0.01;mesh.rotation.y += 0.01;renderer.render(scene, camera);}
OrbitControls
With import maps or build tools, OrbitControls works directly:
import { OrbitControls } from "three/addons/controls/OrbitControls.js";const controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;// Update in animation looprenderer.setAnimationLoop(() => {controls.update();renderer.render(scene, camera);});
Custom Camera Controls (Alternative)
For lightweight custom controls without importing OrbitControls:
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 sceneconst 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 wheelrenderer.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:
const raycaster = new THREE.Raycaster();const mouse = new THREE.Vector2();const clickableObjects = []; // Array of meshes that can be clicked// Update mouse positionwindow.addEventListener("mousemove", (event) => {mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;});// Detect clickswindow.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 loopfunction animate() {requestAnimationFrame(animate);raycaster.setFromCamera(mouse, camera);const intersects = raycaster.intersectObjects(clickableObjects);// Reset all objectsclickableObjects.forEach((obj) => {obj.scale.set(1, 1, 1);});// Highlight hovered objectif (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
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)
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
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: trueon 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
outputEncodinginstead ofoutputColorSpace(renamed in r152) - ❌ 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 - ❌ Using
THREE.Clockwithout consideringTHREE.Timer(recommended in r183)
Example Workflow
User: "Create an interactive 3D sphere that responds to mouse movement"
- Setup: Import Three.js, create scene/camera/renderer
- Geometry: Create
SphereGeometry(1, 32, 32)for smooth sphere - Material: Use
MeshStandardMaterialfor realistic look - Lighting: Add ambient + directional lights
- Interaction: Track mouse position, update camera
- Animation: Rotate sphere, render continuously
- Responsive: Add window resize handler
- 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:
// Enable shadows on rendererrenderer.shadowMap.enabled = true;renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows// Light that casts shadowsconst directionalLight = new THREE.DirectionalLight(0xffffff, 1);directionalLight.position.set(5, 10, 5);directionalLight.castShadow = true;// Configure shadow qualitydirectionalLight.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 shadowsmesh.castShadow = true;mesh.receiveShadow = true;// Ground plane receives shadowsconst 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:
// Create environment map from cubemapconst 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 materialsscene.background = envMap; // Optional: use as skybox// Or apply to specific materialsconst material = new THREE.MeshStandardMaterial({metalness: 1.0,roughness: 0.1,envMap: envMap,});
Tone Mapping & Output Encoding:
// Improve color accuracy and HDR renderingrenderer.toneMapping = THREE.ACESFilmicToneMapping;renderer.toneMappingExposure = 1.0;renderer.outputColorSpace = THREE.SRGBColorSpace; // Was outputEncoding in older versions// Makes colors more vibrant and realistic
Fog for Depth:
// Linear fogscene.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
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
Post-processing effects are available via import maps or build tools. See threejs-postprocessing skill for EffectComposer, bloom, DOF, and more.
Group Objects
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:
- Import Three.js via import maps or build tools
- Initialize scene, camera, renderer
- Create geometry + material = mesh
- Add lighting if using lit materials
- Implement animation loop (prefer
setAnimationLoop) - Handle window resize
- Set
renderer.outputColorSpace = THREE.SRGBColorSpace
Follow these patterns for reliable, performant 3D experiences.
Modern Three.js Practices (r183)
Modular Imports
// With npm/vite/webpack:import * as THREE from "three";import { OrbitControls } from "three/addons/controls/OrbitControls.js";import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
WebGPU Renderer (Alternative)
Three.js r183 includes a WebGPU renderer as an alternative to WebGL:
import { WebGPURenderer } from "three/addons/renderers/webgpu/WebGPURenderer.js";const renderer = new WebGPURenderer({ antialias: true });await renderer.init();renderer.setSize(window.innerWidth, window.innerHeight);
WebGPU uses TSL (Three.js Shading Language) instead of GLSL for custom shaders. See threejs-shaders for details.
Timer (r183 Recommended)
THREE.Timer is recommended over THREE.Clock as of r183:
const timer = new THREE.Timer();renderer.setAnimationLoop(() => {timer.update();const delta = timer.getDelta();const elapsed = timer.getElapsed();mesh.rotation.y += delta;renderer.render(scene, camera);});
Benefits over Clock:
- Not affected by page visibility (pauses when tab is hidden)
- Cleaner API design
- Better integration with
setAnimationLoop
Animation Libraries (GSAP Integration)
// Smooth timeline-based animationsimport 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
// Sync 3D animations with page scrolllet scrollY = window.scrollY;window.addEventListener("scroll", () => {scrollY = window.scrollY;});function animate() {requestAnimationFrame(animate);// Rotate based on scroll positionmesh.rotation.y = scrollY * 0.001;// Move camera through scenecamera.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
// Level of Detail (LOD)const lod = new THREE.LOD();lod.addLevel(highDetailMesh, 0); // Close uplod.addLevel(mediumDetailMesh, 10); // Medium distancelod.addLevel(lowDetailMesh, 50); // Far awayscene.add(lod);// Instanced meshes for many identical objectsconst geometry = new THREE.BoxGeometry();const material = new THREE.MeshStandardMaterial();const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);// Set transforms for each instanceconst 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
// 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 materialsgltf.scene.traverse((child) => {if (child.isMesh) {child.castShadow = true;child.receiveShadow = true;}});});
When to Use What
Import Map Approach:
- Quick prototypes and demos
- Educational content
- Artifacts and embedded experiences
- No build step required
Production Build Approach:
- Client projects and portfolios
- Complex applications
- Performance-critical applications
- Team collaboration with version control
Recommended Production Stack
Three.js r183 + Vite├── GSAP (animations)├── React Three Fiber (optional - React integration)├── Drei (helper components)├── Leva (debug GUI)└── Post-processing effects
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.