Animation
Animation brings 3D scenes to life through movement, transformation, and time-based changes.
Animation Loop
Basic Animation Loop
function animate() {
requestAnimationFrame(animate);
// Update objects
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// Render scene
renderer.render(scene, camera);
}
animate();
Clock-Based Animation
const clock = new THREE.Clock();
function animate() {
const elapsedTime = clock.getElapsedTime();
const deltaTime = clock.getDelta();
// Time-based animation
cube.rotation.x = elapsedTime * 0.5;
cube.position.y = Math.sin(elapsedTime) * 2;
// Delta time for frame-rate independent animation
cube.position.x += velocity * deltaTime;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Object Transformation
Position Animation
function animate() {
const time = Date.now() * 0.001;
// Linear movement
object.position.x = time;
// Circular movement
object.position.x = Math.cos(time) * 5;
object.position.z = Math.sin(time) * 5;
// Bouncing movement
object.position.y = Math.abs(Math.sin(time * 2)) * 3;
// Smooth interpolation
object.position.lerp(targetPosition, 0.1);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Rotation Animation
function animate() {
const time = Date.now() * 0.001;
// Euler rotation
object.rotation.x = time * 0.5;
object.rotation.y = time * 0.3;
object.rotation.z = time * 0.1;
// Quaternion rotation (smoother)
const quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), time);
object.quaternion.copy(quaternion);
// Smooth rotation interpolation
object.quaternion.slerp(targetQuaternion, 0.1);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Scale Animation
function animate() {
const time = Date.now() * 0.001;
// Uniform scaling
const scale = 1 + Math.sin(time) * 0.5;
object.scale.setScalar(scale);
// Non-uniform scaling
object.scale.x = 1 + Math.sin(time) * 0.3;
object.scale.y = 1 + Math.cos(time) * 0.3;
object.scale.z = 1 + Math.sin(time * 0.5) * 0.2;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Built-in Animation System
AnimationMixer
const mixer = new THREE.AnimationMixer(model);
// Add animation clip
const action = mixer.clipAction(animationClip);
action.play();
// Update mixer in animation loop
const clock = new THREE.Clock();
function animate() {
const deltaTime = clock.getDelta();
mixer.update(deltaTime);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
AnimationClip
// Create keyframe track
const positionKF = new THREE.VectorKeyframeTrack(
'.position',
[0, 1, 2], // Times
[0, 0, 0, 1, 0, 0, 0, 1, 0] // Values [x,y,z, x,y,z, x,y,z]
);
const rotationKF = new THREE.QuaternionKeyframeTrack(
'.quaternion',
[0, 1, 2],
[0, 0, 0, 1, 0, 0, 0.7071, 0.7071, 0, 0, 0, 1]
);
// Create animation clip
const clip = new THREE.AnimationClip('action', 2, [positionKF, rotationKF]);
AnimationAction
const action = mixer.clipAction(clip);
// Play modes
action.setLoop(THREE.LoopOnce);
action.setLoop(THREE.LoopRepeat);
action.setLoop(THREE.LoopPingPong);
// Control playback
action.play();
action.pause();
action.stop();
action.reset();
// Animation properties
action.time = 0.5;
action.timeScale = 2.0;
action.weight = 0.5;
action.repetitions = 3;
action.clampWhenFinished = true;
// Crossfade between actions
action1.crossFadeTo(action2, 0.5);
Tweening Libraries
Tween.js
import { Tween, Easing, Group } from '@tweenjs/tween.js';
const group = new Group();
// Basic tween
const tween = new Tween(object.position, group)
.to({ x: 5, y: 2, z: 0 }, 2000)
.easing(Easing.Quadratic.Out)
.onUpdate(() => {
// Optional update callback
})
.onComplete(() => {
console.log('Animation complete');
})
.start();
// Update tweens in animation loop
function animate() {
group.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Chaining Tweens
const tween1 = new Tween(object.position)
.to({ x: 5 }, 1000)
.easing(Easing.Quadratic.Out);
const tween2 = new Tween(object.position)
.to({ y: 3 }, 1000)
.easing(Easing.Quadratic.In);
const tween3 = new Tween(object.position)
.to({ z: 2 }, 1000)
.easing(Easing.Quadratic.InOut);
// Chain tweens
tween1.chain(tween2);
tween2.chain(tween3);
tween1.start();
Morph Targets
Geometry Morphing
// Create base geometry
const geometry = new THREE.BoxGeometry(1, 1, 1);
// Create morph target
const morphGeometry = new THREE.BoxGeometry(2, 0.5, 1);
geometry.morphAttributes.position = [morphGeometry.attributes.position];
// Create mesh with morph targets
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);
// Animate morph targets
function animate() {
const time = Date.now() * 0.001;
mesh.morphTargetInfluences[0] = (Math.sin(time) + 1) * 0.5;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Skeletal Animation
Bone Animation
// Create bone hierarchy
const bone1 = new THREE.Bone();
const bone2 = new THREE.Bone();
const bone3 = new THREE.Bone();
bone1.add(bone2);
bone2.add(bone3);
// Position bones
bone1.position.set(0, 0, 0);
bone2.position.set(0, 2, 0);
bone3.position.set(0, 2, 0);
// Create skeleton
const skeleton = new THREE.Skeleton([bone1, bone2, bone3]);
// Animate bones
function animate() {
const time = Date.now() * 0.001;
bone1.rotation.z = Math.sin(time) * 0.5;
bone2.rotation.z = Math.sin(time * 1.5) * 0.3;
bone3.rotation.z = Math.sin(time * 2) * 0.2;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Particle Animation
Particle System
const particleCount = 1000;
const particles = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const velocities = new Float32Array(particleCount * 3);
// Initialize particles
for (let i = 0; i < particleCount * 3; i += 3) {
positions[i] = (Math.random() - 0.5) * 10;
positions[i + 1] = (Math.random() - 0.5) * 10;
positions[i + 2] = (Math.random() - 0.5) * 10;
velocities[i] = (Math.random() - 0.5) * 0.02;
velocities[i + 1] = (Math.random() - 0.5) * 0.02;
velocities[i + 2] = (Math.random() - 0.5) * 0.02;
}
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const particleMaterial = new THREE.PointsMaterial({
color: 0xff0000,
size: 0.1,
});
const particleSystem = new THREE.Points(particles, particleMaterial);
scene.add(particleSystem);
// Animate particles
function animate() {
const positions = particleSystem.geometry.attributes.position.array;
for (let i = 0; i < particleCount * 3; i += 3) {
positions[i] += velocities[i];
positions[i + 1] += velocities[i + 1];
positions[i + 2] += velocities[i + 2];
// Boundary check
if (positions[i] > 5 || positions[i] < -5) velocities[i] *= -1;
if (positions[i + 1] > 5 || positions[i + 1] < -5) velocities[i + 1] *= -1;
if (positions[i + 2] > 5 || positions[i + 2] < -5) velocities[i + 2] *= -1;
}
particleSystem.geometry.attributes.position.needsUpdate = true;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Material Animation
Uniform Animation
const shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0.0 },
color: { value: new THREE.Color(0xff0000) },
},
vertexShader: `
uniform float time;
void main() {
vec3 pos = position;
pos.y += sin(time + position.x) * 0.5;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
uniform float time;
uniform vec3 color;
void main() {
gl_FragColor = vec4(color * (0.5 + 0.5 * sin(time)), 1.0);
}
`,
});
function animate() {
const time = Date.now() * 0.001;
shaderMaterial.uniforms.time.value = time;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Performance Optimization
Animation Culling
const frustum = new THREE.Frustum();
const matrix = new THREE.Matrix4();
function animate() {
// Update frustum
matrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(matrix);
// Only animate visible objects
scene.traverse(child => {
if (child.isMesh && frustum.intersectsObject(child)) {
// Animate only visible objects
child.rotation.y += 0.01;
}
});
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Level of Detail (LOD)
const lod = new THREE.LOD();
// Add different detail levels
lod.addLevel(highDetailMesh, 0);
lod.addLevel(mediumDetailMesh, 50);
lod.addLevel(lowDetailMesh, 100);
scene.add(lod);
// Update LOD in animation loop
function animate() {
lod.update(camera);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Animation Utilities
Easing Functions
const Easing = {
linear: t => t,
easeInQuad: t => t * t,
easeOutQuad: t => t * (2 - t),
easeInOutQuad: t => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
easeInCubic: t => t * t * t,
easeOutCubic: t => --t * t * t + 1,
easeInOutCubic: t =>
t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
};
// Use in animation
function animate() {
const time = Date.now() * 0.001;
const t = (Math.sin(time) + 1) * 0.5; // Normalize to 0-1
object.position.x = Easing.easeInOutCubic(t) * 10;
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Animation State Machine
class AnimationStateMachine {
constructor() {
this.currentState = 'idle';
this.states = {
idle: { animation: 'idle', loop: true },
walk: { animation: 'walk', loop: true },
run: { animation: 'run', loop: true },
jump: { animation: 'jump', loop: false },
};
}
setState(newState) {
if (this.states[newState] && newState !== this.currentState) {
this.currentState = newState;
const state = this.states[newState];
// Transition to new animation
const action = mixer.clipAction(state.animation);
action.setLoop(state.loop ? THREE.LoopRepeat : THREE.LoopOnce);
action.reset().fadeIn(0.5).play();
}
}
}