Skip to main content

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();
}
}
}