GoByte Skills Episode 19 of 27, track Motion libraries (1 of 3)

GSAP timelines: animation as data

GoByte Skills #19: a GSAP timeline is a seekable program. Every value is a function of the playhead, so you can scrub, reverse and test any frame in plain Node. Plus the three ways it quietly stops being pure.

Arthur is one of GoByte's characters. This post was drafted by AI agents in Arthur's voice, then fact checked, run and edited by the GoByte team.

Back to top

A timeline is a seekable program, so you can scrub, reverse and render any frame, which is what makes animation testable.

Animation for GoByte Skills #19: A playhead scrubs a timeline forward and back.
Transcript

A playhead scrubs a two tween timeline forward and back while the box it drives always lands at the value the code predicts, because every frame is a function of time.

const { gsap } = require("gsap");
gsap.defaults({ ease: "none", duration: 1 });

const box = { x: 0 };
const tl = gsap.timeline({ paused: true });
tl.to(box, { x: 100 }).to(box, { x: 0 });

console.log(tl.duration()); // 2
tl.seek(0.5);
console.log(box.x);         // 50
tl.progress(0.75);
console.log(box.x);         // 50
tl.seek(1.9).seek(0.25);
console.log(box.x);         // 25

No DOM, no clock, no waiting for frames. GSAP tweens any numeric property of any object, so this runs in plain Node.

Why it works#

A timeline is a tree of tweens, each with a start time and a duration. Put the playhead at t and every child gets a local time, t - start, clamped to its duration. Each tween then writes from + (to - from) * ease(local / duration) (repeat and yoyo only fold the local time first). Every value is a function of the playhead, not of the frames that came before. Jump from 1.9 back to 0.25 and you get exactly what straight playback shows at 0.25. Playing is just a ticker calling that same render with the wall clock.

What that buys you#

A unit test that asserts a value at a time. Scrubbing with a slider or the scroll position. A renderer that seeks frame by frame and screenshots each one, which is how the clip on this post was made, identically on every run.

Where it breaks#

  • Start values are recorded lazily, on a tween's first render. Set box.x = 500 before the first seek and seek(0.5) gives 300: the tween starts from whatever it finds.
  • Callbacks: in GSAP 3, seek() suppresses them by default, while time() and progress() fire the ones they pass over. Same playhead, different side effects.
  • Anything outside the timeline is not seekable. One setTimeout or Math.random() in an onUpdate, and the frame at 3.2 s depends on how you got there.

Rule of thumb#

Hang every moving thing off one timeline and the animation becomes a pure function of time, testable like any other. Skip it and your test plan is "watch it a few times and squint".

Report a mistake

Your product here? Partner with us

Back to top

Discussion

No comments yet. Signed in GoByte members with a verified e-mail can join. Community guidelines

Reading is open to everyone. Commenting and voting need a GoByte account with a verified e-mail.