Skip to content

Timelines

While a single animation moves properties from one state to another, a Timeline groups multiple animations, labels, and callbacks together in order.

Timelines sequence events without manually calculating delay times. The timeline computes the total time automatically, enabling the entire sequence to be paused, played, reversed, or skipped.


Basic sequencing

By default, animations added to a timeline play consecutively. Each animation waits for the previous one to finish before starting.

60 FPS

Instead of using the standalone animate() function, the .animate() method is called directly on the timeline instance:

javascript
import { timeline } from 'animx';

const tl = timeline()

tl.animate('.box', { x: 200, duration: 0.6 })
tl.animate('.box', { y: 100, duration: 0.6 }) // starts after x finishes

The timeline automatically calculates its total duration based on its children.

Simple multi-step animations

If you just need a single element to go through a sequence of values (like a bouncing path or a color cycle), you don't always need a timeline! You can pass an array directly into a Tween's properties: animate('.box', { x: [0, 100, 200, 100, 0] }). See Array Keyframes.


Animating Plain JS Objects

Timelines can also animate non-DOM JavaScript objects. Since a timeline coordinates its child tweens, its this.targets property collects and returns all objects animated by any nested tweens.

X: 0 | Y: 0
60 FPS
javascript
import { timeline } from 'animx';

const character = { x: 0, y: 0 };

const tl = timeline({
  onUpdate: function() {
    // Collects all unique targets from all tweens in the timeline
    const current = this.targets[0];
    drawCharacter(current.x, current.y);
  }
});

tl.animate(character, { x: 100, duration: 1 });
tl.animate(character, { y: 100, duration: 1 });
tl.animate(character, { x: 0, duration: 1 });
tl.animate(character, { y: 0, duration: 1 });

The position parameter

Consecutive playback is not always sufficient. Overlapping animations or synchronized start times are often required.

The position parameter (the third argument in .animate()) controls exactly when an animation starts in the timeline.

0s
1s
2s
3s
4s
default
'<'
'>'
'+=0.4'
'<+=0.5'
0.5
'>+=0.5'
60 FPS

There are three primary ways to define a position:

Absolute time (Number)

An absolute time schedules the animation to start exactly n seconds from the beginning of the timeline.

js
tl.animate('.box', { x: 100 }, 1.5) // Starts exactly at 1.5s

Relative offsets (+= and -=)

When the += or -= prefixes are used, the position is calculated relative to all siblings defined prior to the current animation in the code. The timeline finds the latest active time among these preceding animations and attaches the new animation to that point.

Animations in a timeline are evaluated sequentially from top to bottom. The timeline only accounts for animations defined prior to the current statement. Animations defined subsequently do not impact the current position calculation.

js
// The timeline ends at 10 seconds.
tl.animate('.box-a', { x: 100, duration: 10 }) 

// Evaluates the preceding siblings, finding the 10-second end time, adds 1 second, and starts at 11 seconds.
// The furthest point reached by any animation so far becomes 13s (11s start + 2s duration).
tl.animate('.box-b', { y: 100, duration: 2 }, '+=1') 

// Without a position parameter (equivalent to '+=0'), the animation evaluates preceding siblings, finds the 13-second end time, and starts exactly there.
// The duration of 50 seconds does not affect the start times of the preceding siblings.
tl.animate('.box-c', { opacity: 1, duration: 50 })

Immediate relative offsets (< and >)

While += evaluates all preceding siblings, the < and > symbols evaluate only the single sibling defined immediately prior to the current animation.

  • < (Start): Aligns with the start time of the immediately preceding sibling.
  • > (End): Aligns with the end time of the immediately preceding sibling.

These symbols can be combined with numeric offsets:

js
tl.animate('.box-1', { x: 100, duration: 2 })           
tl.animate('.box-2', { x: 100 }, '<')      // Starts exactly when .box-1 starts
tl.animate('.box-3', { x: 100 }, '>')      // Starts exactly when .box-2 finishes
tl.animate('.box-4', { x: 100 }, '>+=0.5') // Starts 0.5 seconds after .box-3 finishes
tl.animate('.box-5', { x: 100 }, '<+=0.5') // Starts 0.5 seconds after .box-4 starts

Positioning Summary

  • < or > position relative to the single sibling immediately prior in the code.
  • += positions relative to all siblings defined prior in the code.

Labels and callbacks

Labels act as bookmarks for specific points in the timeline. They reference a specific time without hardcoding exact seconds.

The addLabel() method inserts a label at the current time. This label can then be used as the position parameter for other animations to synchronize their start times.

You can also use mathematical offsets relative to labels:

  • 'halfway+0.5' (Starts 0.5s after the 'halfway' label)
  • 'halfway-=1' (Starts 1s before the 'halfway' label)

Additionally, callbacks execute JavaScript code at a specific point in the timeline.

In the demo below, the text highlights exactly when the moving dot passes the markers.

'halfway' label
end callback
60 FPS
javascript
import { timeline } from 'animx';

const tl = timeline()

// 1. Move to 130px
tl.animate('.dot', { x: 130, duration: 1 })

// 2. Drop a label here
tl.addLabel('halfway') 

// 3. Fire a one-off callback (triggers when playing forward)
tl.call(() => console.log('Passed the halfway mark!')) 

// 4. Animate the label with a tiny duration so it scrubs perfectly in both directions
tl.animate('.log1', { opacity: 1, color: '#00d4ff', duration: 0.01 }, '<')

// 5. Continue moving to 260px
tl.animate('.dot', { x: 260, duration: 1 })
tl.call(() => console.log('End reached!'))
tl.animate('.log2', { opacity: 1, color: '#00d4ff', duration: 0.01 }, '<')

// Jump to a label:
// tl.seek('halfway')

.call(callback, params, position)

The .call() method executes a custom function at a specific point in the timeline.

Arguments:

  • callback (Function): The function to execute.
  • params (Array, optional): An array of arguments to pass to the callback function.
  • position (String | Number, optional): Exactly when the callback should fire. Follows the exact same Positioning rules as .animate() (e.g. <, >, +=0.5, etc.). By default, it appends to the end of the timeline.
js
// Fire exactly 2 seconds into the timeline
tl.call(() => console.log('2 seconds passed!'), [], 2)

// Pass arguments to the callback, firing immediately after the previous animation
tl.call((name, status) => {
  console.log(`${name} is ${status}`)
}, ['AnimX', 'Awesome'], '>')

NOTE

Callbacks execute only during forward playback; they do not automatically "undo" actions during backward playback.

The .call() method does not implicitly pass the timeline instance into the parameter array. However, this is automatically bound to the Timeline instance, exposing properties like this.progress or this.duration inside the callback (provided a standard function() {} is used rather than an arrow function).

Best Practice

When changing visual styles (such as display: none or color shifts), a 0-duration .apply() or .animate() is preferred over .call(). This allows AnimX to automatically revert the style changes when the timeline plays in reverse. The .call() method is reserved for external side-effects like playing audio, sending analytics data, or triggering external logic.


Nesting timelines

Timelines can be nested inside other timelines. This pattern keeps complex animation sequences organized.

Functions can return smaller timelines, which are then added directly to a master timeline.

60 FPS
javascript
import { timeline } from 'animx';

function createIntro() {
  const tl = timeline()
  tl.animate('.logo', { y: 0, opacity: 1 })
  export default tl
}

function createOutro() {
  const tl = timeline()
  tl.animate('.logo', { opacity: 0 })
  export default tl
}

// Master sequence controls everything
const master = timeline()
master.add(createIntro())
master.add(createOutro(), '+=2') // wait 2 seconds before the outro

Child timelines automatically follow the play/pause state, speed, and direction of their parent.


Timeline options

When creating a timeline, a configuration object sets its playback behavior. It accepts many of the same flags as a standard tween.

javascript
import { timeline } from 'animx';

const tl = timeline({
  paused: true,        // wait for tl.play()
  repeat: -1,          // loop forever (-1)
  yoyo: true,          // alternate direction on each repeat
  repeatDelay: 1,      // wait 1 second between repeats
  delay: 0.5,          // wait 0.5 seconds before starting the first time
  ease: 'cubic.inOut' // applies easing to the timeline's overall progress!
})

Adding an ease to a timeline curves the flow of time for the entire sequence. Nested animations dynamically adjust their playback speed accordingly.


API Reference

Constructor

javascript
import { timeline } from 'animx';

const tl = timeline(options)

paused

Type: boolean | Default: false If true, the timeline does not start playing automatically.

repeat

Type: number | Default: 0 Number of times to loop (-1 creates an infinite loop).

yoyo

Type: boolean | Default: false If true, reverses playback direction on every other repeat cycle.

delay

Type: number | Default: 0 Initial delay before starting the first cycle (in seconds).

repeatDelay

Type: number | Default: 0 Delay between loop cycles (in seconds).

ease

Type: string | Function | Default: null A timeline-level ease applied to the total duration playback.

onStart

Type: Function | Default: null Fired when the timeline begins playing.

onUpdate

Type: Function | Default: null Fired every frame while the timeline is active.

onComplete

Type: Function | Default: null Fired when the timeline finishes.

Instance Methods

All methods that add animations return the timeline instance (this), allowing for method chaining: tl.animate(...).addLabel(...).animate(...).

animate(targets, vars, position)

Works like the standalone animate(). Adds an animation to the timeline.

animateFrom(targets, vars, position)

Works like the standalone animateFrom(). Adds a from animation.

sequence(targets, from, to, position)

Works like the standalone sequence(). Adds a from-to animation.

apply(targets, vars, position)

Works like the standalone apply(). Adds an instant change.

add(child, position)

Adds an existing Tween, Timeline, Function, or label string.

addLabel(name, position)

Inserts a named label at the given position.

call(fn, params, position)

Inserts a zero-duration callback.

remove(child)

Removes a specific child and recalculates total duration.

clear(labels)

Empties all children. Passing false keeps labels intact.

Playback Methods

play(from)

Starts or resumes playback. A time or label string can be provided to start from a specific point.

pause(atTime)

Pauses playback.

reverse(from)

Plays backward.

seek(timeOrLabel)

Jumps to a specific time (in seconds) or a label string.

kill()

Stops the timeline completely and cleans it up from memory.

Properties

progress

Type: number Get or set the playback progress (0 to 1). Setting this seeks the timeline.

timeScale

Type: number Get or set the playback speed multiplier. 1 is normal, -1 is reverse.

getChildren(nested = false)

Returns an array of { child, startTime }. If nested is true, recursively flattens child Timelines.

remove(child)

Removes a tween or timeline from the timeline and recalculates total duration.

clear(labels = true)

Empties the timeline of all children and optionally removes all labels.

addLabel(name, position)

Inserts a named label at the specified position.

totalDuration

Type: number The absolute length of the timeline (in seconds), including repeats.