Access the AI Animation Generator and premium effects
Upgrade your plan to keep generating animations and unlock premium effects.
Master the art of sequencing complex animations. Timelines are the foundation of sophisticated motion graphics, letting you orchestrate multiple animations with precise control and callbacks.
A timeline is a container that orchestrates the playback of multiple tweens (animations). Instead of managing individual animations with manual delays, timelines provide a unified interface for sequencing, controlling, and coordinating complex animation sequences. They're essential for building sophisticated motion graphics where multiple elements need to move in precise coordination.
When you create a timeline with gsap.timeline(), you get a powerful animation orchestrator. You can add tweens with .to(), .from(), or .fromTo(), and by default they're added sequentially at the end of the previous animation. But timelines give you far more control: you can insert animations at specific times, play multiple animations in parallel, add labels, include callbacks at precise moments, and control playback with play/pause/reverse/seek.
// Create a timeline
const tl = gsap.timeline();
// Add animations in sequence (each waits for previous to end)
tl.to('.box-1', { duration: 0.8, x: 100, ease: 'power2.out' })
.to('.box-2', { duration: 0.8, y: -50, ease: 'power2.out' })
.to('.box-3', { duration: 0.8, rotation: 360, ease: 'back.out' });
// Or use position parameter for parallel animations
tl.to('.box-1', { duration: 1, x: 50 })
.to('.box-2', { duration: 1, y: 50 }, '<'); // '<' = start at same time as previous
// Timeline controls
tl.play();
tl.pause();
tl.reverse();
tl.restart();
Timelines are the difference between animating individual elements independently and orchestrating a coordinated sequence. Once you master timelines, you unlock the ability to create professional-quality motion graphics with elegant, readable code.
Every professional GSAP animation typically uses timelines. They're so fundamental that learning them well is essential to becoming proficient with GSAP. This guide covers everything you need to know.
The position parameter is the most critical and powerful feature of timelines. It determines where in the timeline an animation gets inserted—allowing you to create sequential animations, parallel animations, overlapping animations, or precise timing. Understanding the position parameter is the key to unlocking timeline mastery.
The position parameter is passed as the third argument to timeline methods. It can be an absolute time, a relative offset, a label reference, or a special symbol. Each type gives you different control over the timing relationship between animations.
const tl = gsap.timeline();
// 1. ABSOLUTE TIME - Add at exactly 1 second into timeline
tl.to('.box', { duration: 0.5, x: 100 }, 1);
// 2. RELATIVE TO END - Add after previous ends
tl.to('.box', { duration: 0.5, x: 100 })
.to('.box', { duration: 0.5, y: 50 }, '+=0.5'); // Wait 0.5s after previous ends
// 3. RELATIVE TO START OF PREVIOUS - Overlap animations
tl.to('.box', { duration: 1, x: 100 })
.to('.box', { duration: 1, y: 50 }, '<0.5'); // Start 0.5s before previous ends
// 4. AT START OF PREVIOUS (Parallel)
tl.to('.box', { duration: 1, x: 100 })
.to('.box', { duration: 1, rotate: 360 }, '<'); // Start at same time
// 5. LABEL-BASED - Jump to labeled positions
tl.addLabel('halfway');
tl.to('.box', { duration: 0.5, x: 100 })
.to('.box', { duration: 0.5, y: 50 }, 'halfway+=0.2');
// 6. RELATIVE TO END OF PREVIOUS - Before it ends (negative)
tl.to('.box', { duration: 1, x: 100 })
.to('.box', { duration: 1, scale: 1.2 }, '-=0.5'); // Start 0.5s before previous ends
| Type | Example | Description |
|---|---|---|
| Absolute | 1.5 |
Insert at exactly 1.5 seconds into timeline |
| After previous | '>' or '>=' |
Add after previous animation ends (default) |
| After offset | '+=0.5' |
Add 0.5 seconds after previous ends |
| At start of previous | '<' |
Start at same time as previous (parallel) |
| Before offset | '<0.3' |
Start 0.3s before previous ends (overlap) |
| Overlap (negative) | '-=0.5' |
Start 0.5s before previous ends |
| Label | 'myLabel' |
Jump to a labeled position in timeline |
| Label offset | 'myLabel+=0.3' |
Start 0.3s after a label |
The position parameter is passed as the third argument to .to(), .from(), and .fromTo(). It's the key to creating everything from simple sequences to complex overlapping animations. Master this, and you can orchestrate virtually any animation scenario.
Pro tip: Use relative positioning ('<', '<0.2', '-=0.3') for flexibility. This makes your timelines more maintainable—if you change animation durations, the timing automatically adjusts. Absolute times work, but relative positioning creates more resilient code.
Labels are named waypoints in your timeline that make positioning easier and more readable. Instead of calculating exact times, you name important moments in your animation sequence and reference them by name. This makes your code more maintainable and self-documenting.
Use addLabel() to create a label at the current position in the timeline, then reference that label when adding new animations. You can also offset labels with += and -= syntax, allowing precise positioning relative to named markers.
const tl = gsap.timeline();
// Add animations
tl.to('.box', { duration: 1, x: 100, ease: 'power2.out' })
// Add a label at current timeline position
.addLabel('midpoint')
// Continue animating, using the label for positioning
.to('.box', { duration: 1, y: 50, ease: 'power2.out' })
.addLabel('endPhase')
.to('.box', { duration: 1, rotation: 360, ease: 'back.out' });
// Reference labels with offset
tl.to('.box', { duration: 0.5, scale: 1.2 }, 'midpoint+=0.2');
// Jump to labeled positions
tl.seek('midpoint'); // Jump to midpoint
tl.seek('endPhase'); // Jump to endPhase
tl.seek('midpoint+=0.5'); // Jump to 0.5s after midpoint
// Get time of a label
const midpointTime = tl.getLabelTime('midpoint');
console.log(midpointTime); // Output: 1 (the time in seconds)
'myLabel+=0.5') for precise positioninggetLabelTime() to get the exact time of a label for calculationsTimelines aren't just for one-shot animations—they give you complete playback control. You can play, pause, resume, reverse, restart, seek to specific points, adjust playback speed, and check progress. These controls make timelines perfect for interactive animations where users need to trigger, pause, or manipulate animations.
The most commonly used methods are .play(), .pause(), .reverse(), .restart(), .seek(), .progress(), and .timeScale(). You'll use these constantly in interactive applications.
const tl = gsap.timeline({ paused: true }); // Start paused
tl.to('.box', { duration: 1, x: 100 })
.to('.box', { duration: 1, y: 50 })
.to('.box', { duration: 1, rotation: 360 });
// Playback controls
tl.play(); // Start playing
tl.pause(); // Pause at current position
tl.resume(); // Resume from pause (same as play when paused)
tl.reverse(); // Play in reverse
tl.restart(); // Jump to start and play
tl.paused(true); // Pause (setter)
tl.paused(); // Get paused state (getter)
// Seek to specific time
tl.seek(0.5); // Jump to 0.5 seconds
tl.seek(1.5, false); // Jump without triggering callbacks
// Progress (0-1 scale)
tl.progress(0.5); // Jump to 50% through timeline
console.log(tl.progress()); // Get current progress
// Playback speed
tl.timeScale(1); // Normal speed
tl.timeScale(2); // 2x speed (fast-forward)
tl.timeScale(0.5); // 0.5x speed (slow-motion)
// Duration and time
console.log(tl.duration()); // Total duration in seconds
console.log(tl.time()); // Current playhead position
// Jump to label
tl.seek('labelName');
tl.seek('labelName+=0.5');
Playback:
.play() - Start playing.pause() - Pause at current point.reverse() - Play backwards.restart() - Jump to start and playPosition:
.seek(time) - Jump to specific time.progress(value) - Set position 0-1 scale.time() - Get current playhead timeSpeed:
.timeScale(speed) - Adjust playback speed.duration() - Get total timeline lengthState:
.paused() - Check if paused.isActive() - Check if animatingTimeline configuration lets you set defaults for all animations added to that timeline, reducing repetition and making your code cleaner. You can specify default duration, easing, delay, and other properties that apply to all tweens unless explicitly overridden. You can also configure repeat, yoyo, onComplete callbacks, and more.
Pass a configuration object when creating the timeline with gsap.timeline({ ... }). Any properties you set as defaults will apply to all animations added to that timeline, but individual animations can override defaults by specifying their own values.
// Timeline WITH defaults applied
const tlWithDefaults = gsap.timeline({
defaults: {
duration: 1, // All tweens default to 1s
ease: 'power2.out', // All tweens use power2.out
stagger: 0.2 // Stagger all animations
},
repeat: 1, // Repeat entire timeline once
yoyo: true // Reverse after playing
});
// Add animations - they inherit defaults
tlWithDefaults
.to('.box1', { x: 100 }) // Uses default duration and ease
.to('.box2', { y: 50 }) // Uses default duration and ease
.to('.box3', { rotation: 360 }); // Uses default duration and ease
// Override defaults for specific tween
tlWithDefaults.to('.box4', {
x: 200,
duration: 2, // Override duration
ease: 'elastic.out' // Override ease
});
// Timeline configuration options
gsap.timeline({
delay: 0.5, // Delay before timeline starts
paused: true, // Start paused
repeat: -1, // Repeat infinitely
yoyo: true, // Reverse animations
repeatDelay: 0.5, // Delay between repeats
defaults: {
duration: 1,
ease: 'power2.inOut'
},
onStart: () => console.log('Timeline started'),
onComplete: () => console.log('Timeline finished'),
onUpdate: () => console.log('Timeline updated')
});
Nested timelines let you add a timeline as an animation within another timeline. This is incredibly powerful for organizing complex sequences into reusable components. You can create a timeline for a specific animation sequence, then add that entire timeline to a master timeline, treating it as a single unit that respects the parent timeline's position parameter and timing.
This pattern is essential for building sophisticated animation systems where you want to create reusable, composable animation sequences. A button click animation, a menu open animation, a page transition—each can be built as its own timeline and then orchestrated into a larger sequence.
// Create child timeline 1
const tl1 = gsap.timeline();
tl1.to('.box1', { duration: 0.5, x: 100, ease: 'power2.out' })
.to('.box1', { duration: 0.5, y: 50, ease: 'power2.out' });
// Create child timeline 2
const tl2 = gsap.timeline();
tl2.to('.box2', { duration: 0.5, rotation: 180 })
.to('.box2', { duration: 0.5, scale: 1.2 });
// Create master timeline
const master = gsap.timeline();
// Add child timelines (they animate as units)
master.add(tl1) // Add tl1, then tl2 sequentially
.add(tl2) // This waits for tl1 to finish
.add(tl1, '<'); // Add tl1 again, in parallel with tl2
// You can also use position parameters
master.add(tl1, 0) // Add at 0 seconds
.add(tl2, '+=0.2') // Add 0.2s after tl1 ends
.add(tl1, 'label'); // Add at a labeled position
// Access child timeline properties
console.log(tl1.duration()); // Get duration of child
console.log(master.duration()); // Get total master duration
// Reverse entire structure
master.reverse(); // Reverses all nested animations
// Control master timeline
master.play();
master.pause();
master.seek(2); // Seek through entire nested structure
Callbacks let you execute functions at specific points during timeline playback—onStart (when animation begins), onUpdate (every frame), onComplete (when animation finishes), and onRepeat (when repeating). This is essential for synchronizing animations with other code, triggering next steps, or logging events.
Callbacks can be attached to the timeline itself or to individual tweens. Timeline callbacks are global to the entire sequence, while tween callbacks fire only for that specific animation. You can also use eventCallback() to add or modify callbacks after the timeline is created.
// Create timeline with callbacks
const tl = gsap.timeline({
onStart: () => console.log('Timeline started'),
onUpdate: () => console.log('Frame update'),
onComplete: () => console.log('Timeline complete'),
onRepeat: () => console.log('Timeline repeated')
});
// Add animations
tl.to('.box', {
duration: 1,
x: 100,
// Tween-level callbacks
onStart: () => console.log('Box animation started'),
onComplete: () => console.log('Box animation complete')
});
// Add callback at specific timeline position
tl.call(() => console.log('Halfway through!'), 'halfway');
tl.addLabel('halfway', 0.5);
// Modify callbacks after timeline creation
tl.eventCallback('onComplete', () => {
console.log('New completion callback');
});
// Get current callback
const currentCallback = tl.eventCallback('onComplete');
// Remove callback
tl.eventCallback('onComplete', null);
// Callback context (this)
tl.eventCallback('onComplete', function() {
console.log(this.targets()); // Get animated elements
}, null, tl);
// Useful callback patterns
const tl2 = gsap.timeline({
onComplete: () => {
// Play next timeline
tl2.restart();
// Or trigger other actions
// updateUI();
// loadNextContent();
}
});
Timeline Callbacks:
onStart - When timeline beginsonUpdate - Every frame (performance warning!)onComplete - When timeline finishesonRepeat - When repeatingTimeline Methods:
.call(fn, position) - Execute at position.eventCallback(type, fn) - Set/get callback.getTweensOf(target) - Get tweens targeting elementScrollTrigger integration allows you to tie entire timelines to scroll position. Instead of animations playing on load or on command, they trigger when elements enter the viewport. You can even create "scrubbing" effects where scroll position directly controls timeline progress, creating interactive scroll stories and parallax effects.
Combine timelines with ScrollTrigger by passing a scrollTrigger object to any tween, or to the timeline itself. This creates powerful scroll-driven animations that feel responsive and connected to user interaction. Learn more in our complete ScrollTrigger guide.
Scroll down to trigger animation
// Timeline triggered by scroll
const tl = gsap.timeline({
scrollTrigger: {
trigger: '.trigger-element',
start: 'top 80%', // When top of element hits 80% of viewport
end: 'top 20%', // Animation end point
scrub: 1, // Tie to scroll (1 = 1s smoothing)
markers: true, // Debug markers
toggleActions: 'play none none none'
}
});
tl.to('.box', { duration: 2, x: 100 })
.to('.box', { duration: 2, y: 50 })
.to('.box', { duration: 2, rotation: 360 });
// Scrubbing: Link timeline progress directly to scroll
gsap.timeline({
scrollTrigger: {
trigger: '.section',
start: 'top top',
end: 'bottom bottom',
scrub: true, // Instant (no smoothing)
// or scrub: 2 // 2 seconds of smoothing
}
}).to('.element', { x: 500 });
// Pin element while timeline plays
const tl2 = gsap.timeline({
scrollTrigger: {
trigger: '.hero',
pin: true, // Pin element to viewport
start: 'top top',
end: '+=1500', // Duration of pin (1500px of scroll)
scrub: 1,
snap: {
snapTo: 0.5, // Snap to nearest 0.5 increment
duration: 0.3,
delay: 0
}
}
});
tl2.to('.hero-content', { opacity: 0 });
// Multiple timelines with ScrollTrigger
document.querySelectorAll('.section').forEach((section, i) => {
gsap.timeline({
scrollTrigger: {
trigger: section,
start: 'top 75%'
}
}).from(section, { opacity: 0, y: 50 });
});
For comprehensive ScrollTrigger coverage including markers, advanced positioning, and complex scenarios, see our ScrollTrigger guide.
Set timeline defaults for duration and easing to reduce repetition and make timelines more maintainable. Override only when necessary for specific tweens.
Use labels for important timeline positions instead of absolute times. This makes your code more maintainable when you adjust animation durations.
Build animations as functions that return timelines. Nest them into master timelines. This enables reusable, testable animation components.
Prefer relative position parameters ('<', '+=0.2') over absolute times. They're more flexible when timings change.
In frameworks (React, Vue), kill timelines when components unmount: tl.kill(). Prevents memory leaks and unexpected animations.
Check prefers-reduced-motion and disable animations for users with motion sensitivity. Animations are enhancements, not requirements.
repeat option in the timeline config: gsap.timeline({ repeat: 1 }) repeats once (plays twice total). Use repeat: -1 for infinite repeat. Add yoyo: true to reverse between repeats. You can also set repeatDelay for gaps between cycles.
.progress(value) method on mouse events. Listen to click or drag on a progress bar, calculate the percentage (0-1), and call tl.progress(percentage). This lets users scrub through the timeline by clicking or dragging a progress indicator.
onComplete callback in the timeline config or set it with eventCallback('onComplete', fn). This function fires when the timeline finishes playing. Useful for triggering next steps, updating UI, or playing another timeline.
'-=0.5' means start 0.5s before the previous animation ends (overlap). '+=0.5' means start 0.5s after it ends (gap). '<' means start at the same time. Choose based on the timing relationship you want.
.seek() and .reverse() together, or create a separate timeline for the section you want to control. For precise control, use callbacks and .progress() to position the timeline at specific points.
.to(), .from(), etc. If you need to change existing animations, kill the timeline and recreate it, or design your code to accept parameters that adjust animation values before creation.
You now have all the knowledge to create sophisticated, professional animation sequences. From simple animations to complex nested timelines with scroll integration, you can build anything. The key is practice—start building, experiment with position parameters, and create amazing animations.