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
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.
paused: true is the setup, not an afterthought
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.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 easedtimeScale - 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
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
scrubon the timeline and let it driveprogress()for you rather than wiring scroll toprogress()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), notpause(), which keeps it alive in memory
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, andprogress()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 itsprogress()against scroll position depending on config
reverse() from a dynamic layout needs a rebuild
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.See it running in production
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.
addLabel(), then reference by name to place tweens, seek the playhead, or start playback.