GSAP Timelines Explained — Step-by-Step Guide to Sequencing Animations
%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
A["Getting Started"] --> B["You Are Here: Timelines & Sequencing"]
B --> C["ScrollTrigger"]
B --> D["Easing & Motion Paths"]
A --> B
click A "/frontend/libraries/gsap/gsap-getting-started"
click B "/frontend/libraries/gsap/gsap-timelines-sequencing"
click C "/frontend/libraries/gsap/gsap-scrolltrigger"
click D "/frontend/libraries/gsap/gsap-easing-motion"
GSAP timelines give you a container to sequence, overlap, and orchestrate multiple animations with millisecond precision — replacing messy setTimeout chains with a clean, declarative API.
In this tutorial, you’ll learn how to build multi-step animation sequences, control timing with the position parameter, use labels as bookmarks, nest timelines for modular code, and build a full interactive choreography tool. This is the approach DodaTech engineers use to create the polished multi-step animations in Doda Browser and Durga Antivirus Pro.
What You’ll Learn
- Create timelines and append tweens in sequence
- Use the position parameter to overlap and delay tweens
- Add named labels as bookmark references
- Nest timelines inside other timelines for modular code
- Control playback speed with
timeScale() - Reverse, seek, and jump to specific points
- Avoid the 6 most common timeline mistakes
Why Sequencing Matters
In real JavaScript applications, animations rarely involve a single movement. Think about a modal opening:
Security note: Understanding Gsap Timelines Sequencing helps build more secure applications — a core principle at DodaTech, where tools like Durga Antivirus Pro and Doda Browser rely on solid implementation practices.
- The backdrop fades in
- The modal scales up from the center
- Content fades in with a stagger
- A close button bounces in at the end
Without a timeline, you’d nest setTimeout calls or chain promises. It gets messy fast. A timeline gives you a single object that controls the entire sequence. You can pause it, reverse it, speed it up, or jump to any point.
The same principle applies to DodaZIP’s extraction workflow — progress bar fills, file icons drop in, completion badge bounces. Each step is a tween in a timeline, orchestrated with precision.
Prerequisites
gsap.to(), gsap.from(), duration, and delay. If those terms are new, start with the Getting Started guide.Creating a Timeline — Your First Sequence
Think of a timeline as a playlist. Each tween is a track. By default, tracks play one after another in order.
// Create an empty timeline
const tl = gsap.timeline();
// Add tweens — they'll play in sequence
tl.to(".box", { x: 200, duration: 1 }); // 1. Move right
tl.to(".box", { y: 100, duration: 0.8 }); // 2. Move down
tl.to(".box", { rotation: 360, duration: 0.6 }); // 3. Spin
What happens step by step:
- The timeline starts with no tweens
.to(".box", { x: 200 })appends a tween — it plays immediately when the timeline starts- The second
.to()appends after the first — it waits for the first to finish - The third waits for the second
Why this is better than setTimeout:
// Messy — hard to read, hard to maintain
setTimeout(() => gsap.to(".box", { x: 200 }), 0);
setTimeout(() => gsap.to(".box", { y: 100 }), 1000);
setTimeout(() => gsap.to(".box", { rotation: 360 }), 1800);
// Clean — everything in one place
const tl = gsap.timeline()
.to(".box", { x: 200, duration: 1 })
.to(".box", { y: 100, duration: 0.8 })
.to(".box", { rotation: 360, duration: 0.6 });Adding Tweens — All Three Methods Work
Timelines accept to(), from(), fromTo(), and set():
const tl = gsap.timeline();
// Start — hero fades in from above
tl.from(".hero", { opacity: 0, y: -40, duration: 0.8 });
// Next — title slides up
tl.to(".title", { y: 0, opacity: 1, duration: 0.6 });
// Next — button bounces in
tl.fromTo(".btn",
{ scale: 0 },
{ scale: 1, duration: 0.5, ease: "back.out(2)" }
);
// Set an immediate property (no animation)
tl.set(".badge", { opacity: 1 });The Position Parameter — Your Precision Tool
This is the most powerful feature of timelines. The position parameter controls when a tween starts. By default, it’s "+=0" — right after the previous tween ends. But you can change it to create overlaps, gaps, or simultaneous animations.
const tl = gsap.timeline();
tl.to(".a", { x: 100, duration: 1 });
// .b starts at the SAME time as .a (position 0)
tl.to(".b", { x: 100, duration: 1 }, 0);
// .c starts 0.5s AFTER .a ends
tl.to(".c", { x: 100, duration: 1 }, "+=0.5");
// .d starts 0.3s BEFORE .a ends (overlap!)
tl.to(".d", { x: 100, duration: 1 }, "-=0.3");Position Syntax Reference
| Position | Meaning |
|---|---|
0 | Start immediately (at timeline’s beginning) |
1 | Start 1 second into the timeline |
"+=1" | Start 1 second after the previous tween ends |
"-=0.5" | Start 0.5 seconds before the previous tween ends (overlap) |
"label" | Start at a named label |
"label+=0.5" | Start 0.5 seconds after a named label |
"<" | Start at the same time as the previous tween’s start |
"<0.3" | Start 0.3 seconds after the previous tween’s start |
tl.to(".red", { x: 200, duration: 1 });
tl.to(".blue", { x: 200, duration: 1 }, "-=0.5"); // overlaps by 0.5s
tl.to(".green", { x: 200, duration: 1 }, 0.2); // starts 0.2s into timeline
Why relative positions beat absolute: If you change a tween’s duration earlier in the sequence, absolute positions break. Relative positions ("+=0.5", "-=0.3") automatically adjust.
Labels — Bookmarking Your Timeline
Labels are named markers you can jump to or use as position references.
const tl = gsap.timeline();
tl.to(".intro", { opacity: 1, duration: 0.5 });
tl.addLabel("content"); // bookmark right here
tl.from(".card", { y: 40, opacity: 0, stagger: 0.1, duration: 0.5 });
tl.to(".cta", { scale: 1.1, duration: 0.3 }, "content+=0.2");
// The CTA animation starts 0.2s after the "content" label
What labels let you do:
- Start tweens at known points:
tl.to(".x", {}, "mylabel+=0.5") - Seek to a spot:
tl.seek("mylabel")— jumps the timeline there - Insert new tweens at exact locations later without recalculating timing
Nesting Timelines — Modular Animation Building
Timelines can contain other timelines. Think of it like folders organizing files.
// Create reusable sub-timelines
const fadeIn = gsap.timeline();
fadeIn.from(".card", { opacity: 0, y: 20, stagger: 0.1, duration: 0.4 });
const slideIn = gsap.timeline();
slideIn.from(".sidebar", { x: -200, duration: 0.5 });
slideIn.from(".main", { x: 100, opacity: 0, duration: 0.4 });
// Nest them in a master timeline
const master = gsap.timeline();
master.add(fadeIn);
master.add(slideIn, "-=0.2"); // overlap by 0.2s
Why nest? Each sub-timeline can be:
- Tested independently
- Reused across different scenes
- Treated as a single unit in the parent
Time Scaling — Speed Control
timeScale() controls the playback speed of the entire timeline. This is incredibly useful for debugging or creating slow-motion effects.
const tl = gsap.timeline();
tl.to(".box", { x: 300, duration: 2 });
tl.to(".box", { y: 100, duration: 1 });
// Slow motion — half speed
tl.timeScale(0.5);
// Fast forward — double speed
tl.timeScale(2);
// Pause — effectively zero speed
tl.timeScale(0);Why this matters: In Durga Antivirus Pro’s scan results, the animation might play at 1× normally, but at 2× when the user has seen it before. timeScale() makes this trivial.
Reversing & Seeking — Full Navigation Control
Timelines aren’t locked to forward-only playback. You can reverse, jump, or scrub through them.
const tl = gsap.timeline();
tl.to(".box", { x: 200, duration: 1 });
tl.to(".box", { rotation: 180, duration: 0.6 });
tl.to(".box", { scale: 1.5, duration: 0.4 });
// Play in reverse
tl.reverse();
// Jump to 50% progress
tl.progress(0.5);
// Jump to an exact time
tl.seek(1.2);
// Toggle direction
if (tl.reversed()) {
tl.play();
} else {
tl.reverse();
}Callbacks — Per-Tween and Per-Timeline
Each tween inside a timeline keeps its own callbacks:
const tl = gsap.timeline();
tl.to(".box", {
x: 200, duration: 1,
onStart: () => console.log("Box starts moving"),
onComplete: () => console.log("Box reached 200px")
});
tl.to(".box", {
y: 100, duration: 1,
onComplete: () => console.log("Box dropped")
});You can also set timeline-level callbacks:
const tl = gsap.timeline({
onStart: () => console.log("Full sequence started"),
onComplete: () => console.log("Full sequence complete")
});totalDuration() — Timeline Length
totalDuration() returns the total length of the timeline including all tweens, repeats, and yoyo. You can also stretch or shrink it.
const tl = gsap.timeline();
tl.to(".a", { x: 100, duration: 1 });
tl.to(".b", { x: 200, duration: 1.5 });
tl.to(".c", { x: 50, duration: 0.8 });
console.log(tl.totalDuration()); // 3.3 seconds
// Stretch the entire timeline to exactly 5 seconds
tl.totalDuration(5); // each tween scales proportionally
Common Mistakes
1. Chaining methods incorrectly
Each timeline method returns the timeline itself (fluent API). But if you forget to chain properly:
// Correct — chain returns the timeline
const tl = gsap.timeline()
.to(".a", { x: 100, duration: 1 })
.to(".b", { x: 200, duration: 1 }, "-=0.5");
// Wrong — lose reference
gsap.timeline().to(".a", { x: 100 }); // not assigned to anything!
2. Using absolute time when relative is better
Hardcoded absolute positions like 3.5 break if you edit earlier tweens.
// Fragile
tl.to(".a", { x: 100, duration: 1 });
tl.to(".b", { x: 200, duration: 1.5 }, 1.5); // 1.5 hardcoded
// Robust — using relative position
tl.to(".a", { x: 100, duration: 1 });
tl.to(".b", { x: 200, duration: 1.5 }, "+=0.2"); // adjusts automatically
3. Forgetting that tl.to() defaults to the end
If you want two tweens to play simultaneously, you must specify a position.
const tl = gsap.timeline();
tl.to(".a", { x: 100, duration: 1 });
tl.to(".b", { x: 200, duration: 1 }); // plays AFTER .a, not with it
// Fix: add position 0
tl.to(".b", { x: 200, duration: 1 }, 0);4. Nesting without managing parent-child timing
A nested timeline’s timeScale() or reverse() does not affect the parent unless you control both.
const child = gsap.timeline();
child.to(".inner", { x: 100, duration: 2 });
const parent = gsap.timeline();
parent.add(child);
child.reverse(); // reverses child only, parent keeps going
5. Overlapping positions that create gaps
Double-check with tl.duration() to avoid unintended pauses.
// Gap: tween1 (1s) ends at 1s, tween2 starts at 1.5 — 0.5s gap
const tl = gsap.timeline();
tl.to(".a", { x: 100, duration: 1 });
tl.to(".b", { opacity: 0, duration: 0.5 }, 1.5);6. Killing a timeline without stopping children
tl.kill() stops the parent but doesn’t guarantee child timelines stop cleanly. Use tl.pause() + tl.progress(0) for full cleanup.
Practice Questions
What is the default position when adding a tween to a timeline?
"+=0"— it starts immediately after the previous tween ends. Use an explicit position to overlap or delay.How do you make two tweens play at the same time in a timeline?
Pass position
0or"<"to the second tween. Example:tl.to(".a", {x:100}); tl.to(".b", {x:100}, 0);Why should you prefer relative positions (
"+=0.5") over absolute values (1.5)?Relative positions automatically adjust when you change the duration of earlier tweens. Absolute positions require manual recalculation of every value downstream.
What does
tl.timeScale(2)do?It doubles the playback speed of the entire timeline — all tweens play twice as fast.
0.5gives half-speed (slow motion).How do you loop a timeline forever?
Set
repeat: -1when creating the timeline:gsap.timeline({repeat: -1, yoyo: true}). The yoyo makes it reverse on each repeat for a seamless loop.
Challenge
Create a timeline that animates a loading sequence: three dots that appear one after another (stagger), then pulse together, then fade out. Each dot should be a different color. Use labels and relative positions.
Solution
const tl = gsap.timeline({ repeat: -1 });
tl.from(".dot", {
scale: 0, opacity: 0,
duration: 0.3, stagger: 0.15,
ease: "back.out(2)"
})
.addLabel("dotsIn")
.to(".dot", {
scale: 1.3,
duration: 0.3, yoyo: true, repeat: 1,
ease: "power1.inOut"
})
.addLabel("dotsPulse")
.to(".dot", {
opacity: 0, scale: 0,
duration: 0.3, stagger: 0.1,
ease: "power2.in"
});FAQ
Try It Yourself — Timeline Choreographer
Build an interactive tool with play/pause/reverse controls, speed slider, visual progress bar, and label markers. This is the kind of tool DodaTech engineers use when prototyping multi-step UI animations for Doda Browser features.
What’s Next
Now that you can choreograph multi-step sequences, learn how to tie them to scroll position.
| Tutorial | What You’ll Learn |
|---|---|
| https://tutorials.dodatech.com/frontend/libraries/gsap/gsap-scrolltrigger/ | Scroll-driven animations with ScrollTrigger |
| https://tutorials.dodatech.com/frontend/libraries/gsap/gsap-easing-motion/ | Natural easing and motion paths |
| https://tutorials.dodatech.com/frontend/libraries/gsap/gsap-performance/ | Performance optimization at 60fps |
Related topics: React Animations, Vue Animations, JavaScript Events
What’s Next
Congratulations on completing this Gsap Timelines Sequencing tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro