Timeline Control

Play, pause, reverse, seek, and inspect timeline state programmatically.

Every Motion timeline is a named, registry-backed instance you can retrieve and control from anywhere — event handlers, framework lifecycle hooks, or user interactions.


Retrieving a Timeline

Use the single-argument overload to get an existing timeline by name. This returns the same Timeline instance that was created when the animation was first registered.

typescript
import { Motion } from "@motion.page/sdk";

// Create a timeline on page load, paused
Motion("hero", "#hero", { from: { opacity: 0, y: 40 }, duration: 0.8 })
  .onPageLoad({ paused: true });

// Later — retrieve by name and play manually
const tl = Motion("hero");
tl.play();

The static helpers Motion.get() and Motion.has() are safer for conditional retrieval:

MethodReturnsDescription
Motion("name")TimelineRetrieve a registered timeline — throws if not found
Motion.get("name")Timeline | undefinedSafe retrieval — returns undefined if not found
Motion.has("name")booleanCheck if a timeline is registered
Motion.getNames()string[]All registered timeline names
typescript
// Guard before accessing
if (Motion.has("hero")) {
  Motion.get("hero")?.pause();
}

Playback Methods

All playback methods return this for chaining.

MethodSignatureDescription
.play()play(from?: number): thisPlay forward. Optional from jumps to that time in seconds first
.pause()pause(atTime?: number): thisPause. Optional atTime seeks to that time before pausing
.reverse()reverse(from?: number): thisPlay backward from the current position. Optional from jumps first
.restart()restart(): thisReset to time 0 and play forward
.seek()seek(position: number): thisJump to a time in seconds without changing play state
.kill()kill(clearProps?: boolean): voidDestroy the timeline. clearProps: true (default) restores CSS
.clear()clear(): thisRemove all animations without destroying the timeline instance
typescript
import { Motion } from "@motion.page/sdk";

Motion("banner", "#banner", { from: { opacity: 0, y: 30 }, duration: 1 })
  .onPageLoad({ paused: true });

const tl = Motion("banner");

tl.play();          // play from current position
tl.pause();         // freeze
tl.seek(0.5);       // jump to 0.5s, stay paused
tl.reverse();       // play backward from current position
tl.restart();       // reset to 0 and play

Killing a Timeline

.kill() removes the timeline from the registry and — by default — restores all animated CSS properties to their pre-animation values.

typescript
// Kill and restore CSS (default)
Motion("hero").kill();

// Kill but leave element in its current animated state
Motion("hero").kill(false);

After .kill(), calling Motion("hero") will throw. Use Motion.has("hero") to guard, or Motion.get("hero") which returns undefined safely.


State Inspection

Read timeline state at any point. Methods marked as getter/setter accept an optional value — call with no argument to read, with an argument to set and return this.

MethodSignatureDescription
.isActive()isActive(): booleantrue if the timeline is currently animating
.progress()progress(value?: number): number | thisProgress as 0–1. Pass a value to seek by progress
.time()time(value?: number): number | thisElapsed time in seconds. Pass a value to seek to that time
.duration()duration(): numberTotal timeline duration in seconds (read-only)
.timeScale()timeScale(value?: number): number | thisPlayback speed multiplier. 1 = normal, 2 = double, 0.5 = half
.getName()getName(): string | undefinedThe timeline’s registered name
typescript
const tl = Motion("sequence");

// Read current state
console.log(tl.isActive());   // true | false
console.log(tl.progress());   // 0–1
console.log(tl.time());       // seconds elapsed
console.log(tl.duration());   // total seconds
console.log(tl.timeScale());  // 1 (normal speed)
console.log(tl.getName());    // "sequence"

// Seek to 50% progress
tl.progress(0.5);

// Jump to 2 seconds
tl.time(2);

// Slow down to half speed
tl.timeScale(0.5);

Callbacks

Timeline-Level Callbacks

Chain callback methods on the Timeline instance to react to lifecycle events. These are separate from the trigger — chain them before or after:

MethodSignatureDescription
.onStart(cb)onStart(cb: () => void): thisFires when the timeline begins playing
.onUpdate(cb)onUpdate(cb: (progress: number, time: number) => void): thisFires every frame with current progress (0–1) and time (seconds)
.onComplete(cb)onComplete(cb: () => void): thisFires when the timeline finishes
.call(cb, params?, position?)call(cb, params?, position?): thisInsert a function call at a specific position in the timeline
typescript
import { Motion } from "@motion.page/sdk";

Motion("reveal", ".hero", { from: { opacity: 0, y: 40 }, duration: 0.8 })
  .onStart(() => {
    console.log("Animation started");
  })
  .onUpdate((progress, time) => {
    console.log(`Progress: ${(progress * 100).toFixed(1)}%  Time: ${time.toFixed(2)}s`);
  })
  .onComplete(() => {
    console.log("Animation complete");
  })
  .onPageLoad();

Use .call() to trigger a function at a precise moment within a multi-step timeline:

typescript
Motion("sequence", [
  { target: "#title",    from: { opacity: 0 }, duration: 0.5 },
  { target: "#subtitle", from: { opacity: 0 }, duration: 0.4, position: "+=0.1" },
])
  .call(() => console.log("Title appeared"), [], 0.5)
  .call(() => console.log("Sequence complete"), [], ">")
  .onPageLoad();

Per-Animation Callbacks

Individual animations inside a timeline also support callbacks via AnimationConfig. These fire relative to each animation rather than the timeline as a whole.

PropertyTypeDescription
onStart() => voidFires when this animation begins
onUpdate(progress: number) => voidFires every frame for this animation
onComplete() => voidFires when this animation finishes
onRepeat(repeatCount: number) => voidFires at the start of each repeat cycle
onReverseComplete() => voidFires when this animation completes in reverse
typescript
Motion("loader", ".bar", {
  to: { width: "100%" },
  duration: 2,
  ease: "power1.inOut",
  onUpdate: (progress) => {
    document.querySelector<HTMLElement>("#label")!.textContent =
      `${Math.round(progress * 100)}%`;
  },
  onComplete: () => {
    document.querySelector<HTMLElement>("#label")!.textContent = "Done";
  },
}).play();


Common Patterns

Play/Pause Toggle

typescript
import { Motion } from "@motion.page/sdk";

Motion("panel", "#panel", { from: { opacity: 0, y: 20 }, duration: 0.8 })
  .onPageLoad({ paused: true });

document.querySelector("#toggle")?.addEventListener("click", () => {
  const tl = Motion("panel");
  tl.isActive() ? tl.pause() : tl.play();
});

Progress Bar Driven by Timeline

typescript
import { Motion } from "@motion.page/sdk";

const bar = document.querySelector<HTMLElement>("#progress-bar")!;

Motion("hero-sequence", [
  { target: "#title",    from: { opacity: 0, y: 30 }, duration: 0.6 },
  { target: "#subtitle", from: { opacity: 0 },         duration: 0.5, position: "+=0.1" },
  { target: "#cta",      from: { opacity: 0, y: 10 }, duration: 0.4, position: "+=0.1" },
])
  .onUpdate((progress) => {
    bar.style.width = `${progress * 100}%`;
  })
  .onPageLoad();

Scrubbing with seek()

Bind .seek() to a range input to let users scrub through a timeline manually:

typescript
import { Motion } from "@motion.page/sdk";

Motion("scrub-demo", "#card", {
  from: { x: -200, opacity: 0 },
  to:   { x:  200, opacity: 1 },
  duration: 2,
}).onPageLoad({ paused: true });

document.querySelector<HTMLInputElement>("#scrubber")?.addEventListener("input", (e) => {
  const input = e.target as HTMLInputElement;
  const tl = Motion("scrub-demo");
  tl.seek((Number(input.value) / 100) * tl.duration());
});

Slowing Down or Speeding Up

timeScale adjusts playback speed without changing any configured durations:

typescript
import { Motion } from "@motion.page/sdk";

Motion("entrance", ".modal", { from: { opacity: 0, scale: 0.92 }, duration: 0.6 })
  .onPageLoad({ paused: true });

// Slow motion preview
Motion("entrance").timeScale(0.25).play();

// Normal speed
Motion("entrance").timeScale(1).restart();

Stopping a Repeating Animation

Use onRepeat on a per-animation basis to stop after a set number of observed cycles:

typescript
import { Motion } from "@motion.page/sdk";

let cycles = 0;

Motion("pulse", ".badge", {
  to: { scale: 1.2 },
  duration: 0.4,
  ease: "power1.inOut",
  repeat: { times: -1, yoyo: true },
  onRepeat: () => {
    cycles++;
    if (cycles >= 6) Motion("pulse").kill();
  },
}).play();

Related: Core Concepts · Duration & Delay