| 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):
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:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000,
);
camera.position.z = 5;
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:
function 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 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
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:
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
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:
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);
}
Custom Camera Controls (OrbitControls Alternative)
Since THREE.OrbitControls isn't available on CDN, implement custom controls:
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;
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 };
});
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));
});
Raycasting for Object Selection
Detect mouse clicks and hovers on 3D objects:
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const clickableObjects = [];
window.addEventListener("mousemove", (event) => {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
});
window.addEventListener("click", () => {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(clickableObjects);
if (intersects.length > 0) {
const clickedObject = intersects[0].object;
clickedObject.material.color.set(0xff0000);
}
});
function animate() {
requestAnimationFrame(animate);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(clickableObjects);
clickableObjects.forEach((obj) => {
obj.scale.set(1, 1, 1);
});
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
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
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"
- Setup: Import Three.js (r128), create scene/camera/renderer
- Geometry: Create
SphereGeometry(1, 32, 32) for smooth sphere
- Material: Use
MeshStandardMaterial for 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:
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
scene.add(directionalLight);
mesh.castShadow = true;
mesh.receiveShadow = true;
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:
const loader = new THREE.CubeTextureLoader();
const envMap = loader.load([
"px.jpg",
"nx.jpg",
"py.jpg",
"ny.jpg",
"pz.jpg",
"nz.jpg",
]);
scene.environment = envMap;
scene.background = envMap;
const material = new THREE.MeshStandardMaterial({
metalness: 1.0,
roughness: 0.1,
envMap: envMap,
});
Tone Mapping & Output Encoding:
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputEncoding = THREE.sRGBEncoding;
Fog for Depth:
scene.fog = new THREE.Fog(0xcccccc, 10, 50);
scene.fog = new THREE.FogExp2(0xcccccc, 0.02);
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
While advanced post-processing may not be available in r128 CDN, basic effects can be achieved with shaders and render targets.
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 correct CDN version (r128)
- Initialize scene, camera, renderer
- Create geometry + material = mesh
- Add lighting if using lit materials
- Implement animation loop
- Handle window resize
- 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
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)
import gsap from "gsap";
gsap.to(mesh.position, {
x: 5,
duration: 2,
ease: "power2.inOut",
});
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
let scrollY = window.scrollY;
window.addEventListener("scroll", () => {
scrollY = window.scrollY;
});
function animate() {
requestAnimationFrame(animate);
mesh.rotation.y = scrollY * 0.001;
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
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);
lod.addLevel(mediumDetailMesh, 10);
lod.addLevel(lowDetailMesh, 50);
scene.add(lod);
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial();
const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);
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
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
const loader = new GLTFLoader();
loader.load("model.gltf", (gltf) => {
scene.add(gltf.scene);
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.