threejs-skills
Frontend & Expérience UXCreate 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.
Documentation
Three.js Skills
Systematically create high-quality 3D scenes and interactive experiences using Three.js best practices.
When to Use
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:
// 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:
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:
2. Build Geometry
Choose appropriate geometry types:
Basic Shapes:
BoxGeometry - cubes, rectangular prismsSphereGeometry - spheres, planetsCylinderGeometry - cylinders, tubesPlaneGeometry - flat surfaces, ground planesTorusGeometry - donuts, ringsIMPORTANT: Do NOT use CapsuleGeometry (introduced in r142, not available in r128)
Alternatives for capsules:
CylinderGeometry + 2 SphereGeometrySphereGeometry with adjusted parameters3. 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 reflectionconst 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 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:
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;
// 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:
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
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
BufferGeometry for custom shapes (more efficient)```javascript
geometry.dispose();
material.dispose();
texture.dispose();
```
Visual Quality
antialias: true on renderer for smooth edgesCode Organization
Common Pitfalls to Avoid
THREE.OrbitControls - not available on CDNTHREE.CapsuleGeometry - requires r142+scene.add()renderer.render() in animation loopExample Workflow
User: "Create an interactive 3D sphere that responds to mouse movement"
SphereGeometry(1, 32, 32) for smooth sphereMeshStandardMaterial for realistic lookTroubleshooting
Black screen / Nothing renders:
Poor performance:
Objects not visible:
Advanced Techniques
Visual Polish for Portfolio-Grade Rendering
Shadows:
// 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:
// 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:
// Improve color accuracy and HDR rendering
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputEncoding = THREE.sRGBEncoding;
// Makes colors more vibrant and realisticFog for Depth:
// 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, densityCustom 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:
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
// 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:
Animation Libraries (GSAP Integration)
// 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:
Scroll-Based Interactions
// 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:
Performance Optimization in Production
// 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
// 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):
Production Build Approach:
Recommended Production Stack
Three.js (latest) + Vite/Webpack
├── GSAP (animations)
├── React Three Fiber (optional - React integration)
├── Drei (helper components)
├── Leva (debug GUI)
└── Post-processing effectsThis skill provides CDN-compatible foundations. In production, you'd layer on these modern tools for professional results.
Compétences similaires
Explorez d'autres agents de la catégorie Frontend & Expérience UX
react-flow-node-ts
Create React Flow node components with TypeScript types, handles, and Zustand integration. Use when building custom nodes for React Flow canvas, creating visual workflow editors, or implementing node-based UI components.
avalonia-zafiro-development
Mandatory skills, conventions, and behavioral rules for Avalonia UI development using the Zafiro toolkit.
theme-factory
Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.