Timeline

Timeline controls

Timeline controls are the methods GSAP exposes on a timeline instance to drive its playhead after it's built: `play()`, `pause()`, `reverse()`, `restart()`, `seek()`, `progress()`, `time()`, and `timeScale()`. They turn a static sequence into something you can start, stop, scrub, speed up, or run backward in response to a click, a hover, a drag, or scroll position.

Updated July 1, 2026

Mechanics

How timeline controls work

A timeline has a single playhead. Every control method moves or re-times that playhead. play() runs it forward from wherever it is, pause() freezes it, and reverse() runs it backward toward time 0. The animation itself never gets rebuilt, so switching direction mid-motion picks up from the exact frame you're on rather than snapping.

The seek and speed methods are getter-setters: call them with an argument to set, without one to read. progress(0.5) jumps the playhead to the halfway point, progress() returns where it currently sits. time(2) seeks in seconds, seek("label") jumps to a named label. timeScale(2) plays at double speed, timeScale(0.5) at half. Tweening timeScale instead of setting it is how you ease a marquee to a stop on hover instead of cutting it dead.

script.js

paused: true is the setup, not an afterthought

Build a controllable timeline with gsap.timeline({ paused: true }) so it holds at time 0 until you call play(). Without it the timeline runs the instant it's created, and your click handler ends up replaying something the user already saw.
When

Use timeline controls for

  • Open/close pairs that share one sequence (play() to open, reverse() to close) so the exit is an exact mirror of the entrance
  • Hover pause/resume on loops (marquee, ticker, carousel) via pause() / play() or an eased timeScale
  • Drag-to-scrub, where pointer movement maps to progress() on a paused timeline
  • Play/pause/scrub UI on a scripted animation (a product demo reel, an onboarding sequence)
  • Speed control that stays smooth mid-motion, tuning timeScale() instead of rebuilding the tween
Alternatives

Use something else when

Reaching for a JS animation library at all is already the minority choice, only 18.4% of mobile pages load one against 91.7% that use a plain CSS transition (HTTP Archive Web Almanac, 2024). So use timeline controls when the payback, real playback control, actually earns the dependency.

  • A one-shot entrance that never reverses or replays, just tween it, no timeline needed
  • Scroll-bound progress, put a ScrollTrigger with scrub on the timeline and let it drive progress() for you rather than wiring scroll to progress() by hand
  • A CSS-only hover or state toggle where a transition covers it without shipping GSAP
  • Killing the animation for good, use kill() (frees the playhead and children), not pause(), which keeps it alive in memory
In production

Used in these Annnimate components

Timeline controls are what make these components interactive instead of just playing once and stopping:

  • Marquee runs on a paused, repeating timeline and leans on nearly the whole control set: pause() / play() on mouseenter and mouseleave, timeScale() to set direction and speed, and progress() to map a drag gesture onto the loop so you can throw it and let it settle
  • Counter builds its count-up as a gsap.timeline() and hands the playhead to ScrollTrigger, so the same timeline plays once on enter or scrubs its progress() against scroll position depending on config

reverse() from a dynamic layout needs a rebuild

If a timeline's from/to values depend on measured layout (element width, scroll height), calling reverse() after the layout changed replays stale positions. Rebuild the timeline on each open, or set invalidateOnRefresh on the ScrollTrigger, so the reverse reads current values.
Used in components

See it running in production

FAQ

Common questions

What's the difference between pause() and kill() on a timeline?
pause() stops the playhead but keeps the timeline alive, so you can play() it again later. kill() destroys the timeline and its child tweens, releasing the targets. Use pause() for a loop you'll resume (a hovered marquee), kill() when the animation is done for good or when you're rebuilding it, for example inside a useGSAP cleanup return.
How do I reverse a timeline that hasn't finished playing yet?
Just call reverse() at any point. GSAP reads the current playhead position and runs backward from exactly there, so a half-open drawer closes from 50% rather than jumping to the end first. This mid-motion pickup is the main reason to use one timeline with play()/reverse() instead of two separate open/close tweens that fight each other.
Should I use progress() or seek() to jump to a point in the timeline?
progress() takes a normalized value from 0 to 1 and is the right call for scrubbing (map a drag or scroll fraction straight onto it). seek() and time() take seconds or a label, so they're better when you want to jump to a specific labelled moment. All three move the playhead without changing the paused/playing state, so seeking a playing timeline keeps it playing.
Why does my timeline play immediately instead of waiting for the click?
A timeline starts running the moment it's created unless you pass paused: true in the constructor. Build controllable timelines with gsap.timeline({ paused: true }) and call play() from your handler. Otherwise the sequence has often already finished by the time the user interacts.
Can I change a timeline's speed while it's playing?
Yes. timeScale() is live, timeScale(2) doubles speed, timeScale(0.5) halves it, with no interruption to the motion. For a smooth ramp rather than an instant change, tween the timeScale itself: gsap.to(tl, { timeScale: 0, duration: 0.4 }) eases a loop to a stop. That's how a marquee decelerates on hover instead of cutting out.