In Chapter 6, we explored how to create basic animations by directly manipulating object properties within the animation loop. While effective for simple movements, as our scenes become more complex, managing multiple animations and orchestrating their timing can quickly become a tangled mess. This is where the concept of timelines and sequences becomes invaluable. Think of a timeline as a director's script for your 3D scene, dictating when and how each element should behave over time.
A 'timeline' in animation typically refers to a continuous, often repeating, loop of changes. In Three.js, this is what we achieve with requestAnimationFrame. However, when we talk about 'creating timelines and sequences' in a more structured sense, we're often referring to tools or patterns that allow us to define specific start and end points for animations, control their durations, and group them together. Libraries like GSAP (GreenSock Animation Platform) are exceptionally good at this and are often integrated with Three.js for powerful animation control.
Let's consider a common scenario: animating a series of objects. Without a structured approach, you might find yourself with multiple if statements checking time elapsed for each object. A timeline-based approach allows you to define the animation for each object independently and then specify when each of those animations should begin relative to each other. This leads to cleaner, more maintainable code.
A 'sequence' takes the concept a step further. Instead of just playing animations concurrently on a timeline, a sequence defines a specific order of operations. Animation A plays, then Animation B starts, then Animation C plays. This is crucial for storytelling and creating interactive experiences where events unfold in a precise order.
While Three.js itself doesn't provide a built-in, high-level timeline or sequencing API, it offers the fundamental building blocks. We can leverage the Clock object to keep track of time and manually construct our own simple timelines and sequences. Let's look at an example of how to create a basic sequence where one object animates after another has finished.
const clock = new THREE.Clock();
let animationState = 'waiting'; // 'waiting', 'animating_cube', 'animating_sphere', 'finished'
let cubeAnimating = false;
let sphereAnimating = false;
// Assume you have cube and sphere objects already created
function animate() {
requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
if (animationState === 'waiting') {
// Do nothing, waiting for a trigger or initial start
} else if (animationState === 'animating_cube') {
if (!cubeAnimating) {
// Start cube animation
cubeAnimating = true;
// Set initial properties for cube animation
}
// Update cube animation logic here
// For example, moving the cube upwards
cube.position.y += 10 * deltaTime;
// Check if cube animation is complete
if (cube.position.y >= 5) {
cube.position.y = 5; // Ensure it stops precisely
animationState = 'animating_sphere';
cubeAnimating = false;
}
} else if (animationState === 'animating_sphere') {
if (!sphereAnimating) {
// Start sphere animation
sphereAnimating = true;
// Set initial properties for sphere animation
}
// Update sphere animation logic here
// For example, rotating the sphere
sphere.rotation.y += 0.5 * deltaTime;
// Check if sphere animation is complete
if (sphere.rotation.y >= Math.PI * 2) {
sphere.rotation.y = 0; // Reset for potential re-animation
animationState = 'finished';
sphereAnimating = false;
}
} else if (animationState === 'finished') {
// All animations complete
}
renderer.render(scene, camera);
}
// To start the sequence, you would call:
// animationState = 'animating_cube';
// animate(); // Make sure to call this to start the loop